flwr 1.24.0__py3-none-any.whl → 1.26.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 +1 -1
- flwr/app/__init__.py +4 -1
- flwr/app/message_type.py +29 -0
- flwr/app/metadata.py +5 -2
- flwr/app/user_config.py +19 -0
- flwr/cli/app.py +37 -19
- flwr/cli/app_cmd/publish.py +25 -75
- flwr/cli/app_cmd/review.py +25 -66
- flwr/cli/auth_plugin/auth_plugin.py +5 -10
- flwr/cli/auth_plugin/noop_auth_plugin.py +1 -2
- flwr/cli/auth_plugin/oidc_cli_plugin.py +38 -38
- flwr/cli/build.py +15 -28
- flwr/cli/config/__init__.py +21 -0
- flwr/cli/config/ls.py +71 -0
- flwr/cli/config_migration.py +297 -0
- flwr/cli/config_utils.py +63 -156
- flwr/cli/constant.py +71 -0
- flwr/cli/federation/__init__.py +0 -2
- flwr/cli/federation/ls.py +256 -64
- flwr/cli/flower_config.py +429 -0
- flwr/cli/install.py +23 -62
- flwr/cli/log.py +23 -37
- flwr/cli/login/login.py +29 -63
- flwr/cli/ls.py +72 -61
- flwr/cli/new/new.py +98 -309
- flwr/cli/pull.py +19 -37
- flwr/cli/run/run.py +87 -100
- flwr/cli/run_utils.py +23 -5
- flwr/cli/stop.py +33 -74
- flwr/cli/supernode/ls.py +35 -62
- flwr/cli/supernode/register.py +31 -80
- flwr/cli/supernode/unregister.py +24 -70
- flwr/cli/typing.py +200 -0
- flwr/cli/utils.py +160 -412
- flwr/client/grpc_adapter_client/connection.py +2 -2
- flwr/client/grpc_rere_client/connection.py +9 -6
- flwr/client/grpc_rere_client/grpc_adapter.py +1 -1
- flwr/client/message_handler/message_handler.py +2 -1
- flwr/client/mod/centraldp_mods.py +1 -1
- flwr/client/mod/localdp_mod.py +1 -1
- flwr/client/mod/secure_aggregation/secaggplus_mod.py +1 -1
- flwr/client/rest_client/connection.py +6 -4
- flwr/client/run_info_store.py +2 -1
- flwr/clientapp/client_app.py +2 -1
- flwr/common/__init__.py +3 -2
- flwr/common/args.py +5 -5
- flwr/common/config.py +12 -17
- flwr/common/constant.py +3 -16
- flwr/common/context.py +2 -1
- flwr/common/exit/exit.py +4 -4
- flwr/common/exit/exit_code.py +6 -0
- flwr/common/grpc.py +2 -1
- flwr/common/logger.py +1 -1
- flwr/common/message.py +1 -1
- flwr/common/retry_invoker.py +13 -5
- flwr/common/secure_aggregation/ndarrays_arithmetic.py +5 -2
- flwr/common/serde.py +13 -5
- flwr/common/telemetry.py +1 -1
- flwr/common/typing.py +10 -3
- flwr/compat/client/app.py +6 -9
- flwr/compat/client/grpc_client/connection.py +2 -1
- flwr/compat/common/constant.py +29 -0
- flwr/compat/server/app.py +1 -1
- flwr/proto/clientappio_pb2.py +2 -2
- flwr/proto/clientappio_pb2_grpc.py +104 -88
- flwr/proto/clientappio_pb2_grpc.pyi +140 -80
- flwr/proto/federation_pb2.py +5 -3
- flwr/proto/federation_pb2.pyi +32 -2
- flwr/proto/fleet_pb2.py +10 -10
- flwr/proto/fleet_pb2.pyi +5 -1
- flwr/proto/run_pb2.py +18 -26
- flwr/proto/run_pb2.pyi +10 -58
- flwr/proto/serverappio_pb2.py +2 -2
- flwr/proto/serverappio_pb2_grpc.py +138 -207
- flwr/proto/serverappio_pb2_grpc.pyi +189 -155
- flwr/proto/simulationio_pb2.py +2 -2
- flwr/proto/simulationio_pb2_grpc.py +62 -90
- flwr/proto/simulationio_pb2_grpc.pyi +95 -55
- flwr/server/app.py +7 -13
- flwr/server/compat/grid_client_proxy.py +2 -1
- flwr/server/grid/grpc_grid.py +5 -5
- flwr/server/serverapp/app.py +11 -4
- flwr/server/superlink/fleet/grpc_adapter/grpc_adapter_servicer.py +1 -1
- flwr/server/superlink/fleet/grpc_rere/node_auth_server_interceptor.py +13 -12
- flwr/server/superlink/fleet/message_handler/message_handler.py +42 -2
- flwr/server/superlink/linkstate/__init__.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +36 -10
- flwr/server/superlink/linkstate/linkstate.py +34 -21
- flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
- flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +471 -516
- flwr/server/superlink/linkstate/utils.py +49 -2
- flwr/server/superlink/serverappio/serverappio_servicer.py +1 -33
- flwr/server/superlink/simulation/simulationio_servicer.py +0 -19
- flwr/server/utils/validator.py +1 -1
- flwr/server/workflow/default_workflows.py +2 -1
- flwr/server/workflow/secure_aggregation/secaggplus_workflow.py +1 -1
- flwr/serverapp/strategy/bulyan.py +7 -1
- flwr/serverapp/strategy/dp_fixed_clipping.py +9 -1
- flwr/serverapp/strategy/fedavg.py +1 -1
- flwr/serverapp/strategy/fedxgb_cyclic.py +1 -1
- flwr/simulation/ray_transport/ray_client_proxy.py +2 -6
- flwr/simulation/run_simulation.py +3 -12
- flwr/simulation/simulationio_connection.py +3 -3
- flwr/{common → supercore}/address.py +7 -33
- flwr/supercore/app_utils.py +2 -1
- flwr/supercore/constant.py +27 -2
- flwr/supercore/corestate/{sqlite_corestate.py → sql_corestate.py} +19 -23
- flwr/supercore/credential_store/__init__.py +33 -0
- flwr/supercore/credential_store/credential_store.py +34 -0
- flwr/supercore/credential_store/file_credential_store.py +76 -0
- flwr/{common → supercore}/date.py +0 -11
- flwr/supercore/ffs/disk_ffs.py +1 -1
- flwr/supercore/object_store/object_store_factory.py +14 -6
- flwr/supercore/object_store/{sqlite_object_store.py → sql_object_store.py} +115 -117
- flwr/supercore/sql_mixin.py +315 -0
- flwr/{cli/new/templates → supercore/state}/__init__.py +2 -2
- flwr/{cli/new/templates/app/code/flwr_tune → supercore/state/alembic}/__init__.py +2 -2
- flwr/supercore/state/alembic/env.py +103 -0
- flwr/supercore/state/alembic/script.py.mako +43 -0
- flwr/supercore/state/alembic/utils.py +239 -0
- flwr/{cli/new/templates/app → supercore/state/alembic/versions}/__init__.py +2 -2
- flwr/supercore/state/alembic/versions/rev_2026_01_28_initialize_migration_of_state_tables.py +200 -0
- flwr/supercore/state/schema/README.md +121 -0
- flwr/{cli/new/templates/app/code → supercore/state/schema}/__init__.py +2 -2
- flwr/supercore/state/schema/corestate_tables.py +36 -0
- flwr/supercore/state/schema/linkstate_tables.py +152 -0
- flwr/supercore/state/schema/objectstore_tables.py +90 -0
- flwr/supercore/superexec/run_superexec.py +2 -2
- flwr/supercore/utils.py +225 -0
- flwr/superlink/federation/federation_manager.py +2 -2
- flwr/superlink/federation/noop_federation_manager.py +8 -6
- flwr/superlink/servicer/control/control_grpc.py +2 -0
- flwr/superlink/servicer/control/control_servicer.py +106 -21
- flwr/supernode/cli/flower_supernode.py +2 -1
- flwr/supernode/nodestate/in_memory_nodestate.py +62 -1
- flwr/supernode/nodestate/nodestate.py +45 -0
- flwr/supernode/runtime/run_clientapp.py +14 -14
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +13 -5
- flwr/supernode/start_client_internal.py +17 -10
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/METADATA +8 -8
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/RECORD +144 -184
- flwr/cli/federation/show.py +0 -317
- flwr/cli/new/templates/app/.gitignore.tpl +0 -163
- flwr/cli/new/templates/app/LICENSE.tpl +0 -202
- flwr/cli/new/templates/app/README.baseline.md.tpl +0 -127
- flwr/cli/new/templates/app/README.flowertune.md.tpl +0 -68
- flwr/cli/new/templates/app/README.md.tpl +0 -37
- flwr/cli/new/templates/app/code/__init__.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.py.tpl +0 -1
- flwr/cli/new/templates/app/code/__init__.pytorch_legacy_api.py.tpl +0 -1
- flwr/cli/new/templates/app/code/client.baseline.py.tpl +0 -75
- flwr/cli/new/templates/app/code/client.huggingface.py.tpl +0 -93
- flwr/cli/new/templates/app/code/client.jax.py.tpl +0 -71
- flwr/cli/new/templates/app/code/client.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/client.numpy.py.tpl +0 -46
- flwr/cli/new/templates/app/code/client.pytorch.py.tpl +0 -80
- flwr/cli/new/templates/app/code/client.pytorch_legacy_api.py.tpl +0 -55
- flwr/cli/new/templates/app/code/client.sklearn.py.tpl +0 -108
- flwr/cli/new/templates/app/code/client.tensorflow.py.tpl +0 -82
- flwr/cli/new/templates/app/code/client.xgboost.py.tpl +0 -110
- flwr/cli/new/templates/app/code/dataset.baseline.py.tpl +0 -36
- flwr/cli/new/templates/app/code/flwr_tune/client_app.py.tpl +0 -92
- flwr/cli/new/templates/app/code/flwr_tune/dataset.py.tpl +0 -87
- flwr/cli/new/templates/app/code/flwr_tune/models.py.tpl +0 -56
- flwr/cli/new/templates/app/code/flwr_tune/server_app.py.tpl +0 -73
- flwr/cli/new/templates/app/code/flwr_tune/strategy.py.tpl +0 -78
- flwr/cli/new/templates/app/code/model.baseline.py.tpl +0 -66
- flwr/cli/new/templates/app/code/server.baseline.py.tpl +0 -43
- flwr/cli/new/templates/app/code/server.huggingface.py.tpl +0 -42
- flwr/cli/new/templates/app/code/server.jax.py.tpl +0 -39
- flwr/cli/new/templates/app/code/server.mlx.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.numpy.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.pytorch.py.tpl +0 -41
- flwr/cli/new/templates/app/code/server.pytorch_legacy_api.py.tpl +0 -31
- flwr/cli/new/templates/app/code/server.sklearn.py.tpl +0 -44
- flwr/cli/new/templates/app/code/server.tensorflow.py.tpl +0 -38
- flwr/cli/new/templates/app/code/server.xgboost.py.tpl +0 -56
- flwr/cli/new/templates/app/code/strategy.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/code/task.huggingface.py.tpl +0 -98
- flwr/cli/new/templates/app/code/task.jax.py.tpl +0 -57
- flwr/cli/new/templates/app/code/task.mlx.py.tpl +0 -102
- flwr/cli/new/templates/app/code/task.numpy.py.tpl +0 -7
- flwr/cli/new/templates/app/code/task.pytorch.py.tpl +0 -99
- flwr/cli/new/templates/app/code/task.pytorch_legacy_api.py.tpl +0 -111
- flwr/cli/new/templates/app/code/task.sklearn.py.tpl +0 -67
- flwr/cli/new/templates/app/code/task.tensorflow.py.tpl +0 -52
- flwr/cli/new/templates/app/code/task.xgboost.py.tpl +0 -67
- flwr/cli/new/templates/app/code/utils.baseline.py.tpl +0 -1
- flwr/cli/new/templates/app/pyproject.baseline.toml.tpl +0 -146
- flwr/cli/new/templates/app/pyproject.flowertune.toml.tpl +0 -80
- flwr/cli/new/templates/app/pyproject.huggingface.toml.tpl +0 -65
- flwr/cli/new/templates/app/pyproject.jax.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.mlx.toml.tpl +0 -56
- flwr/cli/new/templates/app/pyproject.numpy.toml.tpl +0 -49
- flwr/cli/new/templates/app/pyproject.pytorch.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.pytorch_legacy_api.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.sklearn.toml.tpl +0 -52
- flwr/cli/new/templates/app/pyproject.tensorflow.toml.tpl +0 -53
- flwr/cli/new/templates/app/pyproject.xgboost.toml.tpl +0 -61
- flwr/common/pyproject.py +0 -42
- flwr/supercore/sqlite_mixin.py +0 -159
- /flwr/{common → supercore}/version.py +0 -0
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
- {flwr-1.24.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
|
@@ -16,22 +16,27 @@
|
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
from os import urandom
|
|
19
|
+
from typing import Any
|
|
19
20
|
|
|
20
21
|
from flwr.common import ConfigRecord, Context, Error, Message, Metadata, now, serde
|
|
21
22
|
from flwr.common.constant import (
|
|
22
23
|
HEARTBEAT_PATIENCE,
|
|
23
24
|
SUPERLINK_NODE_ID,
|
|
24
25
|
ErrorCode,
|
|
25
|
-
MessageType,
|
|
26
26
|
Status,
|
|
27
27
|
SubStatus,
|
|
28
28
|
)
|
|
29
29
|
from flwr.common.message import make_message
|
|
30
|
+
from flwr.common.serde import recorddict_from_proto, recorddict_to_proto
|
|
31
|
+
from flwr.common.serde_utils import error_from_proto, error_to_proto
|
|
30
32
|
from flwr.common.typing import RunStatus
|
|
31
33
|
|
|
32
34
|
# pylint: disable=E0611
|
|
35
|
+
from flwr.proto.error_pb2 import Error as ProtoError
|
|
33
36
|
from flwr.proto.message_pb2 import Context as ProtoContext
|
|
34
37
|
from flwr.proto.recorddict_pb2 import ConfigRecord as ProtoConfigRecord
|
|
38
|
+
from flwr.proto.recorddict_pb2 import RecordDict as ProtoRecordDict
|
|
39
|
+
from flwr.supercore.constant import SYSTEM_MESSAGE_TYPE
|
|
35
40
|
from flwr.supercore.utils import int64_to_uint64, uint64_to_int64
|
|
36
41
|
|
|
37
42
|
# pylint: enable=E0611
|
|
@@ -233,7 +238,7 @@ def create_message_error_unavailable_ins_message(reply_to_message_id: str) -> Me
|
|
|
233
238
|
dst_node_id=SUPERLINK_NODE_ID,
|
|
234
239
|
reply_to_message_id=reply_to_message_id,
|
|
235
240
|
group_id="", # Unknown
|
|
236
|
-
message_type=
|
|
241
|
+
message_type=SYSTEM_MESSAGE_TYPE,
|
|
237
242
|
created_at=now().timestamp(),
|
|
238
243
|
ttl=0,
|
|
239
244
|
)
|
|
@@ -388,3 +393,45 @@ def check_node_availability_for_in_message(
|
|
|
388
393
|
)
|
|
389
394
|
ret_dict[in_message_id] = reply_message
|
|
390
395
|
return ret_dict
|
|
396
|
+
|
|
397
|
+
|
|
398
|
+
def message_to_dict(message: Message) -> dict[str, Any]:
|
|
399
|
+
"""Transform Message to dict."""
|
|
400
|
+
result = {
|
|
401
|
+
"message_id": message.metadata.message_id,
|
|
402
|
+
"group_id": message.metadata.group_id,
|
|
403
|
+
"run_id": message.metadata.run_id,
|
|
404
|
+
"src_node_id": message.metadata.src_node_id,
|
|
405
|
+
"dst_node_id": message.metadata.dst_node_id,
|
|
406
|
+
"reply_to_message_id": message.metadata.reply_to_message_id,
|
|
407
|
+
"created_at": message.metadata.created_at,
|
|
408
|
+
"delivered_at": message.metadata.delivered_at,
|
|
409
|
+
"ttl": message.metadata.ttl,
|
|
410
|
+
"message_type": message.metadata.message_type,
|
|
411
|
+
"content": None,
|
|
412
|
+
"error": None,
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
if message.has_content():
|
|
416
|
+
result["content"] = recorddict_to_proto(message.content).SerializeToString()
|
|
417
|
+
else:
|
|
418
|
+
result["error"] = error_to_proto(message.error).SerializeToString()
|
|
419
|
+
|
|
420
|
+
return result
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
def dict_to_message(message_dict: dict[str, Any]) -> Message:
|
|
424
|
+
"""Transform dict to Message."""
|
|
425
|
+
content, error = None, None
|
|
426
|
+
if (b_content := message_dict.pop("content", None)) is not None:
|
|
427
|
+
content = recorddict_from_proto(ProtoRecordDict.FromString(b_content))
|
|
428
|
+
if (b_error := message_dict.pop("error", None)) is not None:
|
|
429
|
+
error = error_from_proto(ProtoError.FromString(b_error))
|
|
430
|
+
|
|
431
|
+
# Metadata constructor doesn't allow passing created_at. We set it later
|
|
432
|
+
metadata = Metadata(
|
|
433
|
+
**{k: v for k, v in message_dict.items() if k not in ["delivered_at"]}
|
|
434
|
+
)
|
|
435
|
+
msg = make_message(metadata=metadata, content=content, error=error)
|
|
436
|
+
msg.metadata.delivered_at = message_dict.get("delivered_at", "")
|
|
437
|
+
return msg
|
|
@@ -36,7 +36,6 @@ from flwr.common.serde import (
|
|
|
36
36
|
message_from_proto,
|
|
37
37
|
message_to_proto,
|
|
38
38
|
run_status_from_proto,
|
|
39
|
-
run_status_to_proto,
|
|
40
39
|
run_to_proto,
|
|
41
40
|
)
|
|
42
41
|
from flwr.common.typing import Fab, RunStatus
|
|
@@ -55,7 +54,6 @@ from flwr.proto.appio_pb2 import ( # pylint: disable=E0611
|
|
|
55
54
|
RequestTokenRequest,
|
|
56
55
|
RequestTokenResponse,
|
|
57
56
|
)
|
|
58
|
-
from flwr.proto.fab_pb2 import GetFabRequest, GetFabResponse # pylint: disable=E0611
|
|
59
57
|
from flwr.proto.heartbeat_pb2 import ( # pylint: disable=E0611
|
|
60
58
|
SendAppHeartbeatRequest,
|
|
61
59
|
SendAppHeartbeatResponse,
|
|
@@ -76,8 +74,6 @@ from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
|
|
76
74
|
from flwr.proto.run_pb2 import ( # pylint: disable=E0611
|
|
77
75
|
GetRunRequest,
|
|
78
76
|
GetRunResponse,
|
|
79
|
-
GetRunStatusRequest,
|
|
80
|
-
GetRunStatusResponse,
|
|
81
77
|
UpdateRunStatusRequest,
|
|
82
78
|
UpdateRunStatusResponse,
|
|
83
79
|
)
|
|
@@ -88,7 +84,7 @@ from flwr.proto.serverappio_pb2 import ( # pylint: disable=E0611
|
|
|
88
84
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
|
89
85
|
from flwr.server.superlink.utils import abort_if
|
|
90
86
|
from flwr.server.utils.validator import validate_message
|
|
91
|
-
from flwr.supercore.ffs import
|
|
87
|
+
from flwr.supercore.ffs import FfsFactory
|
|
92
88
|
from flwr.supercore.object_store import NoObjectInStoreError, ObjectStoreFactory
|
|
93
89
|
|
|
94
90
|
|
|
@@ -315,19 +311,6 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
|
|
|
315
311
|
|
|
316
312
|
return GetRunResponse(run=run_to_proto(run))
|
|
317
313
|
|
|
318
|
-
def GetFab(
|
|
319
|
-
self, request: GetFabRequest, context: grpc.ServicerContext
|
|
320
|
-
) -> GetFabResponse:
|
|
321
|
-
"""Get FAB from Ffs."""
|
|
322
|
-
log(DEBUG, "ServerAppIoServicer.GetFab")
|
|
323
|
-
|
|
324
|
-
ffs: Ffs = self.ffs_factory.ffs()
|
|
325
|
-
if result := ffs.get(request.hash_str):
|
|
326
|
-
fab = Fab(request.hash_str, result[0], result[1])
|
|
327
|
-
return GetFabResponse(fab=fab_to_proto(fab))
|
|
328
|
-
|
|
329
|
-
raise ValueError(f"Found no FAB with hash: {request.hash_str}")
|
|
330
|
-
|
|
331
314
|
def PullAppInputs(
|
|
332
315
|
self, request: PullAppInputsRequest, context: grpc.ServicerContext
|
|
333
316
|
) -> PullAppInputsResponse:
|
|
@@ -434,21 +417,6 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
|
|
|
434
417
|
state.add_serverapp_log(request.run_id, merged_logs)
|
|
435
418
|
return PushLogsResponse()
|
|
436
419
|
|
|
437
|
-
def GetRunStatus(
|
|
438
|
-
self, request: GetRunStatusRequest, context: grpc.ServicerContext
|
|
439
|
-
) -> GetRunStatusResponse:
|
|
440
|
-
"""Get the status of a run."""
|
|
441
|
-
log(DEBUG, "ServerAppIoServicer.GetRunStatus")
|
|
442
|
-
state = self.state_factory.state()
|
|
443
|
-
|
|
444
|
-
# Get run status from LinkState
|
|
445
|
-
run_statuses = state.get_run_status(set(request.run_ids))
|
|
446
|
-
run_status_dict = {
|
|
447
|
-
run_id: run_status_to_proto(run_status)
|
|
448
|
-
for run_id, run_status in run_statuses.items()
|
|
449
|
-
}
|
|
450
|
-
return GetRunStatusResponse(run_status_dict=run_status_dict)
|
|
451
|
-
|
|
452
420
|
def SendAppHeartbeat(
|
|
453
421
|
self, request: SendAppHeartbeatRequest, context: grpc.ServicerContext
|
|
454
422
|
) -> SendAppHeartbeatResponse:
|
|
@@ -29,7 +29,6 @@ from flwr.common.serde import (
|
|
|
29
29
|
context_to_proto,
|
|
30
30
|
fab_to_proto,
|
|
31
31
|
run_status_from_proto,
|
|
32
|
-
run_status_to_proto,
|
|
33
32
|
run_to_proto,
|
|
34
33
|
)
|
|
35
34
|
from flwr.common.typing import Fab, RunStatus
|
|
@@ -57,8 +56,6 @@ from flwr.proto.run_pb2 import ( # pylint: disable=E0611
|
|
|
57
56
|
GetFederationOptionsResponse,
|
|
58
57
|
GetRunRequest,
|
|
59
58
|
GetRunResponse,
|
|
60
|
-
GetRunStatusRequest,
|
|
61
|
-
GetRunStatusResponse,
|
|
62
59
|
UpdateRunStatusRequest,
|
|
63
60
|
UpdateRunStatusResponse,
|
|
64
61
|
)
|
|
@@ -219,22 +216,6 @@ class SimulationIoServicer(simulationio_pb2_grpc.SimulationIoServicer):
|
|
|
219
216
|
)
|
|
220
217
|
return UpdateRunStatusResponse()
|
|
221
218
|
|
|
222
|
-
def GetRunStatus(
|
|
223
|
-
self, request: GetRunStatusRequest, context: ServicerContext
|
|
224
|
-
) -> GetRunStatusResponse:
|
|
225
|
-
"""Get status of requested runs."""
|
|
226
|
-
log(DEBUG, "SimultionIoServicer.GetRunStatus")
|
|
227
|
-
state = self.state_factory.state()
|
|
228
|
-
|
|
229
|
-
statuses = state.get_run_status(set(request.run_ids))
|
|
230
|
-
|
|
231
|
-
return GetRunStatusResponse(
|
|
232
|
-
run_status_dict={
|
|
233
|
-
run_id: run_status_to_proto(status)
|
|
234
|
-
for run_id, status in statuses.items()
|
|
235
|
-
}
|
|
236
|
-
)
|
|
237
|
-
|
|
238
219
|
def PushLogs(
|
|
239
220
|
self, request: PushLogsRequest, context: grpc.ServicerContext
|
|
240
221
|
) -> PushLogsResponse:
|
flwr/server/utils/validator.py
CHANGED
|
@@ -21,6 +21,7 @@ from logging import INFO, WARN
|
|
|
21
21
|
from typing import cast
|
|
22
22
|
|
|
23
23
|
import flwr.common.recorddict_compat as compat
|
|
24
|
+
from flwr.app.message_type import MessageType
|
|
24
25
|
from flwr.common import (
|
|
25
26
|
ArrayRecord,
|
|
26
27
|
Code,
|
|
@@ -32,7 +33,7 @@ from flwr.common import (
|
|
|
32
33
|
Message,
|
|
33
34
|
log,
|
|
34
35
|
)
|
|
35
|
-
from flwr.common.constant import
|
|
36
|
+
from flwr.common.constant import MessageTypeLegacy
|
|
36
37
|
|
|
37
38
|
from ..client_proxy import ClientProxy
|
|
38
39
|
from ..compat.app_utils import start_update_client_manager_thread
|
|
@@ -21,12 +21,12 @@ from logging import DEBUG, ERROR, INFO, WARN
|
|
|
21
21
|
from typing import cast
|
|
22
22
|
|
|
23
23
|
import flwr.common.recorddict_compat as compat
|
|
24
|
+
from flwr.app.message_type import MessageType
|
|
24
25
|
from flwr.common import (
|
|
25
26
|
ConfigRecord,
|
|
26
27
|
Context,
|
|
27
28
|
FitRes,
|
|
28
29
|
Message,
|
|
29
|
-
MessageType,
|
|
30
30
|
NDArrays,
|
|
31
31
|
RecordDict,
|
|
32
32
|
bytes_to_ndarray,
|
|
@@ -185,7 +185,13 @@ class Bulyan(FedAvg):
|
|
|
185
185
|
|
|
186
186
|
# Convert to ArrayRecord
|
|
187
187
|
arrays = ArrayRecord(
|
|
188
|
-
dict(
|
|
188
|
+
dict(
|
|
189
|
+
zip(
|
|
190
|
+
array_keys,
|
|
191
|
+
(Array(np.asarray(arr)) for arr in aggregated_ndarrays),
|
|
192
|
+
strict=True,
|
|
193
|
+
)
|
|
194
|
+
)
|
|
189
195
|
)
|
|
190
196
|
|
|
191
197
|
# Aggregate MetricRecords
|
|
@@ -22,6 +22,8 @@ from abc import ABC
|
|
|
22
22
|
from collections.abc import Iterable
|
|
23
23
|
from logging import INFO, WARNING
|
|
24
24
|
|
|
25
|
+
import numpy as np
|
|
26
|
+
|
|
25
27
|
from flwr.common import Array, ArrayRecord, ConfigRecord, Message, MetricRecord, log
|
|
26
28
|
from flwr.common.differential_privacy import (
|
|
27
29
|
add_gaussian_noise_inplace,
|
|
@@ -216,7 +218,13 @@ class DifferentialPrivacyServerSideFixedClipping(DifferentialPrivacyFixedClippin
|
|
|
216
218
|
)
|
|
217
219
|
# Replace content while preserving keys
|
|
218
220
|
reply.content[arr_name] = ArrayRecord(
|
|
219
|
-
dict(
|
|
221
|
+
dict(
|
|
222
|
+
zip(
|
|
223
|
+
record.keys(),
|
|
224
|
+
(Array(np.asarray(v)) for v in reply_ndarrays),
|
|
225
|
+
strict=True,
|
|
226
|
+
)
|
|
227
|
+
)
|
|
220
228
|
)
|
|
221
229
|
log(
|
|
222
230
|
INFO,
|
|
@@ -18,11 +18,11 @@
|
|
|
18
18
|
from collections.abc import Callable, Iterable
|
|
19
19
|
from logging import INFO, WARNING
|
|
20
20
|
|
|
21
|
+
from flwr.app import MessageType
|
|
21
22
|
from flwr.common import (
|
|
22
23
|
ArrayRecord,
|
|
23
24
|
ConfigRecord,
|
|
24
25
|
Message,
|
|
25
|
-
MessageType,
|
|
26
26
|
MetricRecord,
|
|
27
27
|
RecordDict,
|
|
28
28
|
log,
|
|
@@ -19,11 +19,11 @@ from collections.abc import Callable, Iterable
|
|
|
19
19
|
from logging import INFO
|
|
20
20
|
from typing import cast
|
|
21
21
|
|
|
22
|
+
from flwr.app import MessageType
|
|
22
23
|
from flwr.common import (
|
|
23
24
|
ArrayRecord,
|
|
24
25
|
ConfigRecord,
|
|
25
26
|
Message,
|
|
26
|
-
MessageType,
|
|
27
27
|
MetricRecord,
|
|
28
28
|
RecordDict,
|
|
29
29
|
log,
|
|
@@ -19,16 +19,12 @@ import traceback
|
|
|
19
19
|
from logging import ERROR
|
|
20
20
|
|
|
21
21
|
from flwr import common
|
|
22
|
+
from flwr.app.message_type import MessageType
|
|
22
23
|
from flwr.client import ClientFnExt
|
|
23
24
|
from flwr.client.run_info_store import DeprecatedRunInfoStore
|
|
24
25
|
from flwr.clientapp.client_app import ClientApp
|
|
25
26
|
from flwr.common import DEFAULT_TTL, Message, Metadata, RecordDict, now
|
|
26
|
-
from flwr.common.constant import
|
|
27
|
-
NUM_PARTITIONS_KEY,
|
|
28
|
-
PARTITION_ID_KEY,
|
|
29
|
-
MessageType,
|
|
30
|
-
MessageTypeLegacy,
|
|
31
|
-
)
|
|
27
|
+
from flwr.common.constant import NUM_PARTITIONS_KEY, PARTITION_ID_KEY, MessageTypeLegacy
|
|
32
28
|
from flwr.common.logger import log
|
|
33
29
|
from flwr.common.message import make_message
|
|
34
30
|
from flwr.common.recorddict_compat import (
|
|
@@ -28,6 +28,7 @@ from pathlib import Path
|
|
|
28
28
|
from queue import Empty, Queue
|
|
29
29
|
from typing import Any, cast
|
|
30
30
|
|
|
31
|
+
from flwr.app.user_config import UserConfig
|
|
31
32
|
from flwr.cli.config_utils import load_and_validate
|
|
32
33
|
from flwr.cli.utils import get_sha256_hash
|
|
33
34
|
from flwr.clientapp import ClientApp
|
|
@@ -39,7 +40,7 @@ from flwr.common.logger import (
|
|
|
39
40
|
update_console_handler,
|
|
40
41
|
warn_deprecated_feature_with_example,
|
|
41
42
|
)
|
|
42
|
-
from flwr.common.typing import Run, RunStatus
|
|
43
|
+
from flwr.common.typing import Run, RunStatus
|
|
43
44
|
from flwr.server.grid import Grid, InMemoryGrid
|
|
44
45
|
from flwr.server.run_serverapp import run as _run
|
|
45
46
|
from flwr.server.server_app import ServerApp
|
|
@@ -115,17 +116,7 @@ def run_simulation_from_cli() -> None:
|
|
|
115
116
|
sys.exit("Simulation Engine cannot start.")
|
|
116
117
|
|
|
117
118
|
# Load pyproject.toml
|
|
118
|
-
config,
|
|
119
|
-
app_path / "pyproject.toml", check_module=False
|
|
120
|
-
)
|
|
121
|
-
if errors:
|
|
122
|
-
raise ValueError(errors)
|
|
123
|
-
|
|
124
|
-
if warnings:
|
|
125
|
-
log(WARNING, warnings)
|
|
126
|
-
|
|
127
|
-
if config is None:
|
|
128
|
-
raise ValueError("Config extracted from FAB's pyproject.toml is not valid")
|
|
119
|
+
config, _ = load_and_validate(app_path / "pyproject.toml", check_module=False)
|
|
129
120
|
|
|
130
121
|
# Get ClientApp and SeverApp components
|
|
131
122
|
app_components = config["tool"]["flwr"]["app"]["components"]
|
|
@@ -23,7 +23,7 @@ import grpc
|
|
|
23
23
|
from flwr.common.constant import SIMULATIONIO_API_DEFAULT_CLIENT_ADDRESS
|
|
24
24
|
from flwr.common.grpc import create_channel, on_channel_state_change
|
|
25
25
|
from flwr.common.logger import log
|
|
26
|
-
from flwr.common.retry_invoker import
|
|
26
|
+
from flwr.common.retry_invoker import make_simple_grpc_retry_invoker, wrap_stub
|
|
27
27
|
from flwr.proto.simulationio_pb2_grpc import SimulationIoStub # pylint: disable=E0611
|
|
28
28
|
|
|
29
29
|
|
|
@@ -49,7 +49,7 @@ class SimulationIoConnection:
|
|
|
49
49
|
self._cert = root_certificates
|
|
50
50
|
self._grpc_stub: SimulationIoStub | None = None
|
|
51
51
|
self._channel: grpc.Channel | None = None
|
|
52
|
-
self._retry_invoker =
|
|
52
|
+
self._retry_invoker = make_simple_grpc_retry_invoker()
|
|
53
53
|
|
|
54
54
|
@property
|
|
55
55
|
def _is_connected(self) -> bool:
|
|
@@ -75,7 +75,7 @@ class SimulationIoConnection:
|
|
|
75
75
|
)
|
|
76
76
|
self._channel.subscribe(on_channel_state_change)
|
|
77
77
|
self._grpc_stub = SimulationIoStub(self._channel)
|
|
78
|
-
|
|
78
|
+
wrap_stub(self._grpc_stub, self._retry_invoker)
|
|
79
79
|
log(DEBUG, "[SimulationIO] Connected to %s", self._addr)
|
|
80
80
|
|
|
81
81
|
def _disconnect(self) -> None:
|
|
@@ -15,12 +15,9 @@
|
|
|
15
15
|
"""Flower IP address utils."""
|
|
16
16
|
|
|
17
17
|
|
|
18
|
-
import re
|
|
19
18
|
import socket
|
|
20
19
|
from ipaddress import ip_address
|
|
21
20
|
|
|
22
|
-
import grpc
|
|
23
|
-
|
|
24
21
|
IPV6: int = 6
|
|
25
22
|
|
|
26
23
|
|
|
@@ -105,33 +102,10 @@ def is_port_in_use(address: str) -> bool:
|
|
|
105
102
|
return False
|
|
106
103
|
|
|
107
104
|
|
|
108
|
-
def
|
|
109
|
-
"""
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
"ipv4:127.0.0.1:56789" for IPv4 and "ipv6:[2001:db8::1]:54321" for IPv6.
|
|
116
|
-
|
|
117
|
-
Returns
|
|
118
|
-
-------
|
|
119
|
-
str
|
|
120
|
-
If one of the format matches, the function will return the client's IP address,
|
|
121
|
-
otherwise, it will raise a ValueError.
|
|
122
|
-
"""
|
|
123
|
-
peer: str = context.peer()
|
|
124
|
-
# Match IPv4: "ipv4:IP:port"
|
|
125
|
-
ipv4_match = re.match(r"^ipv4:(?P<ip>[^:]+):", peer)
|
|
126
|
-
if ipv4_match:
|
|
127
|
-
return ipv4_match.group("ip")
|
|
128
|
-
|
|
129
|
-
# Match IPv6: "ipv6:[IP]:port"
|
|
130
|
-
ipv6_match = re.match(r"^ipv6:\[(?P<ip>[^\]]+)\]:", peer)
|
|
131
|
-
if ipv6_match:
|
|
132
|
-
return ipv6_match.group("ip")
|
|
133
|
-
|
|
134
|
-
raise ValueError(
|
|
135
|
-
f"Unsupported peer address format: {peer} for the transport protocol. "
|
|
136
|
-
"The supported formats are ipv4:IP:port and ipv6:[IP]:port."
|
|
137
|
-
)
|
|
105
|
+
def resolve_bind_address(address: str) -> str:
|
|
106
|
+
"""Replace bind-all addresses (0.0.0.0, ::) with localhost (127.0.0.1, ::1)."""
|
|
107
|
+
if address.startswith("[::]"):
|
|
108
|
+
return address.replace("[::]", "[::1]", 1)
|
|
109
|
+
if address.startswith("0.0.0.0"):
|
|
110
|
+
return address.replace("0.0.0.0", "127.0.0.1", 1)
|
|
111
|
+
return address
|
flwr/supercore/app_utils.py
CHANGED
flwr/supercore/constant.py
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
|
|
18
18
|
from __future__ import annotations
|
|
19
19
|
|
|
20
|
-
from flwr.common.constant import FLWR_DIR
|
|
20
|
+
from flwr.common.constant import FLWR_DIR, NOOP_ACCOUNT_NAME
|
|
21
21
|
|
|
22
22
|
# Top-level key in YAML config for exec plugin settings
|
|
23
23
|
EXEC_PLUGIN_SECTION = "exec_plugin"
|
|
@@ -25,11 +25,17 @@ EXEC_PLUGIN_SECTION = "exec_plugin"
|
|
|
25
25
|
# Flower in-memory Python-based database name
|
|
26
26
|
FLWR_IN_MEMORY_DB_NAME = ":flwr-in-memory:"
|
|
27
27
|
|
|
28
|
+
# Flower in-memory SQLite database URL
|
|
29
|
+
FLWR_IN_MEMORY_SQLITE_DB_URL = "sqlite:///:memory:"
|
|
30
|
+
|
|
28
31
|
# Constants for Hub
|
|
29
32
|
APP_ID_PATTERN = r"^@[A-Za-z0-9_.-]+/[A-Za-z0-9_.-]+$"
|
|
30
33
|
APP_VERSION_PATTERN = r"^\d+\.\d+\.\d+$"
|
|
31
34
|
PLATFORM_API_URL = "https://api.flower.ai/v1"
|
|
32
35
|
|
|
36
|
+
# SuperGrid constants
|
|
37
|
+
SUPERGRID_ADDRESS = "supergrid.flower.ai"
|
|
38
|
+
|
|
33
39
|
# Specification for app publishing
|
|
34
40
|
APP_PUBLISH_INCLUDE_PATTERNS = (
|
|
35
41
|
"**/*.py",
|
|
@@ -52,11 +58,30 @@ MIME_MAP = {
|
|
|
52
58
|
}
|
|
53
59
|
|
|
54
60
|
# Constants for federations
|
|
55
|
-
NOOP_FEDERATION = "default"
|
|
61
|
+
NOOP_FEDERATION = f"@{NOOP_ACCOUNT_NAME}/default"
|
|
62
|
+
NOOP_FEDERATION_DESCRIPTION = "A federation for testing and development purposes."
|
|
56
63
|
|
|
57
64
|
# Constants for exit handling
|
|
58
65
|
FORCE_EXIT_TIMEOUT_SECONDS = 5 # Used in `flwr_exit` function
|
|
59
66
|
|
|
67
|
+
# Constants for message processing timing
|
|
68
|
+
MESSAGE_TIME_ENTRY_MAX_AGE_SECONDS = 3600
|
|
69
|
+
|
|
70
|
+
|
|
71
|
+
# System message type
|
|
72
|
+
SYSTEM_MESSAGE_TYPE = "system"
|
|
73
|
+
|
|
74
|
+
# SQLite PRAGMA settings for optimal performance and correctness
|
|
75
|
+
SQLITE_PRAGMAS = (
|
|
76
|
+
("busy_timeout", "5000"), # Retry lock acquisition for up to 5s before SQLITE_BUSY
|
|
77
|
+
("journal_mode", "WAL"), # Enable Write-Ahead Logging for better concurrency
|
|
78
|
+
("synchronous", "NORMAL"),
|
|
79
|
+
("foreign_keys", "ON"),
|
|
80
|
+
("cache_size", "-64000"), # 64MB cache
|
|
81
|
+
("temp_store", "MEMORY"), # In-memory temp tables
|
|
82
|
+
("mmap_size", "268435456"), # 256MB memory-mapped I/O
|
|
83
|
+
)
|
|
84
|
+
|
|
60
85
|
|
|
61
86
|
class NodeStatus:
|
|
62
87
|
"""Event log writer types."""
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
# Copyright
|
|
1
|
+
# Copyright 2026 Flower Labs GmbH. All Rights Reserved.
|
|
2
2
|
#
|
|
3
3
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
4
|
# you may not use this file except in compliance with the License.
|
|
@@ -12,36 +12,31 @@
|
|
|
12
12
|
# See the License for the specific language governing permissions and
|
|
13
13
|
# limitations under the License.
|
|
14
14
|
# ==============================================================================
|
|
15
|
-
"""
|
|
15
|
+
"""SQLAlchemy-based CoreState implementation."""
|
|
16
16
|
|
|
17
17
|
|
|
18
18
|
import secrets
|
|
19
|
-
import sqlite3
|
|
20
19
|
from typing import cast
|
|
21
20
|
|
|
21
|
+
from sqlalchemy import MetaData, text
|
|
22
|
+
from sqlalchemy.exc import IntegrityError
|
|
23
|
+
|
|
22
24
|
from flwr.common import now
|
|
23
25
|
from flwr.common.constant import (
|
|
24
26
|
FLWR_APP_TOKEN_LENGTH,
|
|
25
27
|
HEARTBEAT_DEFAULT_INTERVAL,
|
|
26
28
|
HEARTBEAT_PATIENCE,
|
|
27
29
|
)
|
|
28
|
-
from flwr.supercore.
|
|
30
|
+
from flwr.supercore.sql_mixin import SqlMixin
|
|
31
|
+
from flwr.supercore.state.schema.corestate_tables import create_corestate_metadata
|
|
29
32
|
from flwr.supercore.utils import int64_to_uint64, uint64_to_int64
|
|
30
33
|
|
|
31
34
|
from ..object_store import ObjectStore
|
|
32
35
|
from .corestate import CoreState
|
|
33
36
|
|
|
34
|
-
SQL_CREATE_TABLE_TOKEN_STORE = """
|
|
35
|
-
CREATE TABLE IF NOT EXISTS token_store (
|
|
36
|
-
run_id INTEGER PRIMARY KEY,
|
|
37
|
-
token TEXT UNIQUE NOT NULL,
|
|
38
|
-
active_until REAL
|
|
39
|
-
);
|
|
40
|
-
"""
|
|
41
|
-
|
|
42
37
|
|
|
43
|
-
class
|
|
44
|
-
"""
|
|
38
|
+
class SqlCoreState(CoreState, SqlMixin):
|
|
39
|
+
"""SQLAlchemy-based CoreState implementation."""
|
|
45
40
|
|
|
46
41
|
def __init__(self, database_path: str, object_store: ObjectStore) -> None:
|
|
47
42
|
super().__init__(database_path)
|
|
@@ -52,9 +47,9 @@ class SqliteCoreState(CoreState, SqliteMixin):
|
|
|
52
47
|
"""Return the ObjectStore instance used by this CoreState."""
|
|
53
48
|
return self._object_store
|
|
54
49
|
|
|
55
|
-
def
|
|
56
|
-
"""Return
|
|
57
|
-
return (
|
|
50
|
+
def get_metadata(self) -> MetaData:
|
|
51
|
+
"""Return SQLAlchemy MetaData needed for CoreState tables."""
|
|
52
|
+
return create_corestate_metadata()
|
|
58
53
|
|
|
59
54
|
def create_token(self, run_id: int) -> str | None:
|
|
60
55
|
"""Create a token for the given run ID."""
|
|
@@ -63,7 +58,8 @@ class SqliteCoreState(CoreState, SqliteMixin):
|
|
|
63
58
|
active_until = current + HEARTBEAT_DEFAULT_INTERVAL
|
|
64
59
|
query = """
|
|
65
60
|
INSERT INTO token_store (run_id, token, active_until)
|
|
66
|
-
VALUES (:run_id, :token, :active_until)
|
|
61
|
+
VALUES (:run_id, :token, :active_until)
|
|
62
|
+
RETURNING token;
|
|
67
63
|
"""
|
|
68
64
|
data = {
|
|
69
65
|
"run_id": uint64_to_int64(run_id),
|
|
@@ -71,10 +67,10 @@ class SqliteCoreState(CoreState, SqliteMixin):
|
|
|
71
67
|
"active_until": active_until,
|
|
72
68
|
}
|
|
73
69
|
try:
|
|
74
|
-
self.query(query, data)
|
|
75
|
-
|
|
70
|
+
rows = self.query(query, data)
|
|
71
|
+
return cast(str, rows[0]["token"])
|
|
72
|
+
except IntegrityError:
|
|
76
73
|
return None # Token already created for this run ID
|
|
77
|
-
return token
|
|
78
74
|
|
|
79
75
|
def verify_token(self, run_id: int, token: str) -> bool:
|
|
80
76
|
"""Verify a token for the given run ID."""
|
|
@@ -128,14 +124,14 @@ class SqliteCoreState(CoreState, SqliteMixin):
|
|
|
128
124
|
"""
|
|
129
125
|
current = now().timestamp()
|
|
130
126
|
|
|
131
|
-
with self.
|
|
127
|
+
with self.session() as session:
|
|
132
128
|
# Delete expired tokens and get their run_ids and active_until timestamps
|
|
133
129
|
query = """
|
|
134
130
|
DELETE FROM token_store
|
|
135
131
|
WHERE active_until < :current
|
|
136
132
|
RETURNING run_id, active_until;
|
|
137
133
|
"""
|
|
138
|
-
rows =
|
|
134
|
+
rows = session.execute(text(query), {"current": current}).mappings().all()
|
|
139
135
|
expired_records = [
|
|
140
136
|
(int64_to_uint64(row["run_id"]), row["active_until"]) for row in rows
|
|
141
137
|
]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
# Copyright 2026 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
|
+
"""Credential store for Flower."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from .credential_store import CredentialStore
|
|
19
|
+
from .file_credential_store import FileCredentialStore
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def get_credential_store() -> CredentialStore:
|
|
23
|
+
"""Get the credential store instance.
|
|
24
|
+
|
|
25
|
+
Currently, only FileCredentialStore is implemented.
|
|
26
|
+
"""
|
|
27
|
+
return FileCredentialStore()
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
__all__ = [
|
|
31
|
+
"CredentialStore",
|
|
32
|
+
"get_credential_store",
|
|
33
|
+
]
|