flwr-nightly 1.19.0.dev20250521__py3-none-any.whl → 1.19.0.dev20250523__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/client/grpc_adapter_client/connection.py +4 -4
- flwr/client/grpc_rere_client/connection.py +4 -4
- flwr/client/rest_client/connection.py +4 -4
- flwr/common/inflatable.py +23 -0
- flwr/common/inflatable_grpc_utils.py +2 -0
- flwr/compat/client/app.py +2 -2
- flwr/proto/run_pb2.py +19 -27
- flwr/proto/run_pb2.pyi +0 -51
- flwr/proto/serverappio_pb2.py +2 -2
- flwr/proto/serverappio_pb2_grpc.py +0 -34
- flwr/proto/serverappio_pb2_grpc.pyi +0 -13
- flwr/server/app.py +12 -1
- flwr/server/superlink/fleet/grpc_rere/fleet_servicer.py +6 -1
- flwr/server/superlink/serverappio/serverappio_grpc.py +3 -0
- flwr/server/superlink/serverappio/serverappio_servicer.py +7 -32
- flwr/supercore/object_store/__init__.py +23 -0
- flwr/supercore/object_store/in_memory_object_store.py +65 -0
- flwr/supercore/object_store/object_store.py +86 -0
- flwr/supercore/object_store/object_store_factory.py +44 -0
- flwr/{client/supernode → supernode/cli}/__init__.py +3 -5
- flwr/{client/supernode/app.py → supernode/cli/flower_supernode.py} +2 -10
- flwr/{client → supernode}/start_client_internal.py +179 -301
- {flwr_nightly-1.19.0.dev20250521.dist-info → flwr_nightly-1.19.0.dev20250523.dist-info}/METADATA +1 -1
- {flwr_nightly-1.19.0.dev20250521.dist-info → flwr_nightly-1.19.0.dev20250523.dist-info}/RECORD +28 -24
- {flwr_nightly-1.19.0.dev20250521.dist-info → flwr_nightly-1.19.0.dev20250523.dist-info}/entry_points.txt +1 -1
- /flwr/{client → compat/client}/grpc_client/__init__.py +0 -0
- /flwr/{client → compat/client}/grpc_client/connection.py +0 -0
- {flwr_nightly-1.19.0.dev20250521.dist-info → flwr_nightly-1.19.0.dev20250523.dist-info}/WHEEL +0 -0
@@ -45,10 +45,10 @@ def grpc_adapter( # pylint: disable=R0913,too-many-positional-arguments
|
|
45
45
|
tuple[
|
46
46
|
Callable[[], Optional[Message]],
|
47
47
|
Callable[[Message], None],
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
48
|
+
Callable[[], Optional[int]],
|
49
|
+
Callable[[], None],
|
50
|
+
Callable[[int], Run],
|
51
|
+
Callable[[str, int], Fab],
|
52
52
|
]
|
53
53
|
]:
|
54
54
|
"""Primitives for request/response-based interaction with a server via GrpcAdapter.
|
@@ -74,10 +74,10 @@ def grpc_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
74
74
|
tuple[
|
75
75
|
Callable[[], Optional[Message]],
|
76
76
|
Callable[[Message], None],
|
77
|
-
|
78
|
-
|
79
|
-
|
80
|
-
|
77
|
+
Callable[[], Optional[int]],
|
78
|
+
Callable[[], None],
|
79
|
+
Callable[[int], Run],
|
80
|
+
Callable[[str, int], Fab],
|
81
81
|
]
|
82
82
|
]:
|
83
83
|
"""Primitives for request/response-based interaction with a server.
|
@@ -87,10 +87,10 @@ def http_request_response( # pylint: disable=R0913,R0914,R0915,R0917
|
|
87
87
|
tuple[
|
88
88
|
Callable[[], Optional[Message]],
|
89
89
|
Callable[[Message], None],
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
90
|
+
Callable[[], Optional[int]],
|
91
|
+
Callable[[], None],
|
92
|
+
Callable[[int], Run],
|
93
|
+
Callable[[str, int], Fab],
|
94
94
|
]
|
95
95
|
]:
|
96
96
|
"""Primitives for request/response-based interaction with a server.
|
flwr/common/inflatable.py
CHANGED
@@ -112,6 +112,29 @@ def _get_object_body(object_content: bytes) -> bytes:
|
|
112
112
|
return object_content.split(HEAD_BODY_DIVIDER, 1)[1]
|
113
113
|
|
114
114
|
|
115
|
+
def is_valid_sha256_hash(object_id: str) -> bool:
|
116
|
+
"""Check if the given string is a valid SHA-256 hash.
|
117
|
+
|
118
|
+
Parameters
|
119
|
+
----------
|
120
|
+
object_id : str
|
121
|
+
The string to check.
|
122
|
+
|
123
|
+
Returns
|
124
|
+
-------
|
125
|
+
bool
|
126
|
+
``True`` if the string is a valid SHA-256 hash, ``False`` otherwise.
|
127
|
+
"""
|
128
|
+
if len(object_id) != 64:
|
129
|
+
return False
|
130
|
+
try:
|
131
|
+
# If base 16 int conversion succeeds, it's a valid hexadecimal str
|
132
|
+
int(object_id, 16)
|
133
|
+
return True
|
134
|
+
except ValueError:
|
135
|
+
return False
|
136
|
+
|
137
|
+
|
115
138
|
def get_object_type_from_object_content(object_content: bytes) -> str:
|
116
139
|
"""Return object type from bytes."""
|
117
140
|
return get_object_head_values_from_object_content(object_content)[0]
|
@@ -31,6 +31,7 @@ from .inflatable import (
|
|
31
31
|
get_object_head_values_from_object_content,
|
32
32
|
get_object_id,
|
33
33
|
)
|
34
|
+
from .message import Message
|
34
35
|
from .record import Array, ArrayRecord, ConfigRecord, MetricRecord, RecordDict
|
35
36
|
|
36
37
|
# Helper registry that maps names of classes to their type
|
@@ -38,6 +39,7 @@ inflatable_class_registry: dict[str, type[InflatableObject]] = {
|
|
38
39
|
Array.__qualname__: Array,
|
39
40
|
ArrayRecord.__qualname__: ArrayRecord,
|
40
41
|
ConfigRecord.__qualname__: ConfigRecord,
|
42
|
+
Message.__qualname__: Message,
|
41
43
|
MetricRecord.__qualname__: MetricRecord,
|
42
44
|
RecordDict.__qualname__: RecordDict,
|
43
45
|
}
|
flwr/compat/client/app.py
CHANGED
@@ -41,7 +41,6 @@ from flwr.client.clientapp.clientappio_servicer import (
|
|
41
41
|
ClientAppIoServicer,
|
42
42
|
)
|
43
43
|
from flwr.client.grpc_adapter_client.connection import grpc_adapter
|
44
|
-
from flwr.client.grpc_client.connection import grpc_connection
|
45
44
|
from flwr.client.grpc_rere_client.connection import grpc_request_response
|
46
45
|
from flwr.client.message_handler.message_handler import handle_control_message
|
47
46
|
from flwr.client.numpy_client import NumPyClient
|
@@ -69,6 +68,7 @@ from flwr.common.grpc import generic_create_grpc_server
|
|
69
68
|
from flwr.common.logger import log, warn_deprecated_feature
|
70
69
|
from flwr.common.retry_invoker import RetryInvoker, RetryState, exponential
|
71
70
|
from flwr.common.typing import Fab, Run, RunNotRunningException, UserConfig
|
71
|
+
from flwr.compat.client.grpc_client.connection import grpc_connection
|
72
72
|
from flwr.proto.clientappio_pb2_grpc import add_ClientAppIoServicer_to_server
|
73
73
|
from flwr.supernode.nodestate import NodeStateFactory
|
74
74
|
|
@@ -794,7 +794,7 @@ def _init_connection(transport: Optional[str], server_address: str) -> tuple[
|
|
794
794
|
elif transport == TRANSPORT_TYPE_GRPC_ADAPTER:
|
795
795
|
connection, error_type = grpc_adapter, RpcError
|
796
796
|
elif transport == TRANSPORT_TYPE_GRPC_BIDI:
|
797
|
-
connection, error_type = grpc_connection, RpcError
|
797
|
+
connection, error_type = grpc_connection, RpcError # type: ignore[assignment]
|
798
798
|
else:
|
799
799
|
raise ValueError(
|
800
800
|
f"Unknown transport type: {transport} (possible: {TRANSPORT_TYPES})"
|
flwr/proto/run_pb2.py
CHANGED
@@ -18,7 +18,7 @@ from flwr.proto import recorddict_pb2 as flwr_dot_proto_dot_recorddict__pb2
|
|
18
18
|
from flwr.proto import transport_pb2 as flwr_dot_proto_dot_transport__pb2
|
19
19
|
|
20
20
|
|
21
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xce\x02\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\x12\x12\n\npending_at\x18\x06 \x01(\t\x12\x13\n\x0bstarting_at\x18\x07 \x01(\t\x12\x12\n\nrunning_at\x18\x08 \x01(\t\x12\x13\n\x0b\x66inished_at\x18\t \x01(\t\x12%\n\x06status\x18\n \x01(\x0b\x32\x15.flwr.proto.RunStatus\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"@\n\tRunStatus\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x12\n\nsub_status\x18\x02 \x01(\t\x12\x0f\n\x07\x64\x65tails\x18\x03 \x01(\t\"
|
21
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x14\x66lwr/proto/run.proto\x12\nflwr.proto\x1a\x14\x66lwr/proto/fab.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x1b\x66lwr/proto/recorddict.proto\x1a\x1a\x66lwr/proto/transport.proto\"\xce\x02\n\x03Run\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12\x0e\n\x06\x66\x61\x62_id\x18\x02 \x01(\t\x12\x13\n\x0b\x66\x61\x62_version\x18\x03 \x01(\t\x12<\n\x0foverride_config\x18\x04 \x03(\x0b\x32#.flwr.proto.Run.OverrideConfigEntry\x12\x10\n\x08\x66\x61\x62_hash\x18\x05 \x01(\t\x12\x12\n\npending_at\x18\x06 \x01(\t\x12\x13\n\x0bstarting_at\x18\x07 \x01(\t\x12\x12\n\nrunning_at\x18\x08 \x01(\t\x12\x13\n\x0b\x66inished_at\x18\t \x01(\t\x12%\n\x06status\x18\n \x01(\x0b\x32\x15.flwr.proto.RunStatus\x1aI\n\x13OverrideConfigEntry\x12\x0b\n\x03key\x18\x01 \x01(\t\x12!\n\x05value\x18\x02 \x01(\x0b\x32\x12.flwr.proto.Scalar:\x02\x38\x01\"@\n\tRunStatus\x12\x0e\n\x06status\x18\x01 \x01(\t\x12\x12\n\nsub_status\x18\x02 \x01(\t\x12\x0f\n\x07\x64\x65tails\x18\x03 \x01(\t\"?\n\rGetRunRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\".\n\x0eGetRunResponse\x12\x1c\n\x03run\x18\x01 \x01(\x0b\x32\x0f.flwr.proto.Run\"S\n\x16UpdateRunStatusRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12)\n\nrun_status\x18\x02 \x01(\x0b\x32\x15.flwr.proto.RunStatus\"\x19\n\x17UpdateRunStatusResponse\"F\n\x13GetRunStatusRequest\x12\x1e\n\x04node\x18\x01 \x01(\x0b\x32\x10.flwr.proto.Node\x12\x0f\n\x07run_ids\x18\x02 \x03(\x04\"\xb1\x01\n\x14GetRunStatusResponse\x12L\n\x0frun_status_dict\x18\x01 \x03(\x0b\x32\x33.flwr.proto.GetRunStatusResponse.RunStatusDictEntry\x1aK\n\x12RunStatusDictEntry\x12\x0b\n\x03key\x18\x01 \x01(\x04\x12$\n\x05value\x18\x02 \x01(\x0b\x32\x15.flwr.proto.RunStatus:\x02\x38\x01\"-\n\x1bGetFederationOptionsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"T\n\x1cGetFederationOptionsResponse\x12\x34\n\x12\x66\x65\x64\x65ration_options\x18\x01 \x01(\x0b\x32\x18.flwr.proto.ConfigRecordb\x06proto3')
|
22
22
|
|
23
23
|
_globals = globals()
|
24
24
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
@@ -27,8 +27,6 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
27
27
|
DESCRIPTOR._options = None
|
28
28
|
_globals['_RUN_OVERRIDECONFIGENTRY']._options = None
|
29
29
|
_globals['_RUN_OVERRIDECONFIGENTRY']._serialized_options = b'8\001'
|
30
|
-
_globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._options = None
|
31
|
-
_globals['_CREATERUNREQUEST_OVERRIDECONFIGENTRY']._serialized_options = b'8\001'
|
32
30
|
_globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._options = None
|
33
31
|
_globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_options = b'8\001'
|
34
32
|
_globals['_RUN']._serialized_start=139
|
@@ -37,28 +35,22 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
37
35
|
_globals['_RUN_OVERRIDECONFIGENTRY']._serialized_end=473
|
38
36
|
_globals['_RUNSTATUS']._serialized_start=475
|
39
37
|
_globals['_RUNSTATUS']._serialized_end=539
|
40
|
-
_globals['
|
41
|
-
_globals['
|
42
|
-
_globals['
|
43
|
-
_globals['
|
44
|
-
_globals['
|
45
|
-
_globals['
|
46
|
-
_globals['
|
47
|
-
_globals['
|
48
|
-
_globals['
|
49
|
-
_globals['
|
50
|
-
_globals['
|
51
|
-
_globals['
|
52
|
-
_globals['
|
53
|
-
_globals['
|
54
|
-
_globals['
|
55
|
-
_globals['
|
56
|
-
_globals['
|
57
|
-
_globals['
|
58
|
-
_globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_start=1216
|
59
|
-
_globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_end=1291
|
60
|
-
_globals['_GETFEDERATIONOPTIONSREQUEST']._serialized_start=1293
|
61
|
-
_globals['_GETFEDERATIONOPTIONSREQUEST']._serialized_end=1338
|
62
|
-
_globals['_GETFEDERATIONOPTIONSRESPONSE']._serialized_start=1340
|
63
|
-
_globals['_GETFEDERATIONOPTIONSRESPONSE']._serialized_end=1424
|
38
|
+
_globals['_GETRUNREQUEST']._serialized_start=541
|
39
|
+
_globals['_GETRUNREQUEST']._serialized_end=604
|
40
|
+
_globals['_GETRUNRESPONSE']._serialized_start=606
|
41
|
+
_globals['_GETRUNRESPONSE']._serialized_end=652
|
42
|
+
_globals['_UPDATERUNSTATUSREQUEST']._serialized_start=654
|
43
|
+
_globals['_UPDATERUNSTATUSREQUEST']._serialized_end=737
|
44
|
+
_globals['_UPDATERUNSTATUSRESPONSE']._serialized_start=739
|
45
|
+
_globals['_UPDATERUNSTATUSRESPONSE']._serialized_end=764
|
46
|
+
_globals['_GETRUNSTATUSREQUEST']._serialized_start=766
|
47
|
+
_globals['_GETRUNSTATUSREQUEST']._serialized_end=836
|
48
|
+
_globals['_GETRUNSTATUSRESPONSE']._serialized_start=839
|
49
|
+
_globals['_GETRUNSTATUSRESPONSE']._serialized_end=1016
|
50
|
+
_globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_start=941
|
51
|
+
_globals['_GETRUNSTATUSRESPONSE_RUNSTATUSDICTENTRY']._serialized_end=1016
|
52
|
+
_globals['_GETFEDERATIONOPTIONSREQUEST']._serialized_start=1018
|
53
|
+
_globals['_GETFEDERATIONOPTIONSREQUEST']._serialized_end=1063
|
54
|
+
_globals['_GETFEDERATIONOPTIONSRESPONSE']._serialized_start=1065
|
55
|
+
_globals['_GETFEDERATIONOPTIONSRESPONSE']._serialized_end=1149
|
64
56
|
# @@protoc_insertion_point(module_scope)
|
flwr/proto/run_pb2.pyi
CHANGED
@@ -3,7 +3,6 @@
|
|
3
3
|
isort:skip_file
|
4
4
|
"""
|
5
5
|
import builtins
|
6
|
-
import flwr.proto.fab_pb2
|
7
6
|
import flwr.proto.node_pb2
|
8
7
|
import flwr.proto.recorddict_pb2
|
9
8
|
import flwr.proto.transport_pb2
|
@@ -94,56 +93,6 @@ class RunStatus(google.protobuf.message.Message):
|
|
94
93
|
def ClearField(self, field_name: typing_extensions.Literal["details",b"details","status",b"status","sub_status",b"sub_status"]) -> None: ...
|
95
94
|
global___RunStatus = RunStatus
|
96
95
|
|
97
|
-
class CreateRunRequest(google.protobuf.message.Message):
|
98
|
-
"""CreateRun"""
|
99
|
-
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
100
|
-
class OverrideConfigEntry(google.protobuf.message.Message):
|
101
|
-
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
102
|
-
KEY_FIELD_NUMBER: builtins.int
|
103
|
-
VALUE_FIELD_NUMBER: builtins.int
|
104
|
-
key: typing.Text
|
105
|
-
@property
|
106
|
-
def value(self) -> flwr.proto.transport_pb2.Scalar: ...
|
107
|
-
def __init__(self,
|
108
|
-
*,
|
109
|
-
key: typing.Text = ...,
|
110
|
-
value: typing.Optional[flwr.proto.transport_pb2.Scalar] = ...,
|
111
|
-
) -> None: ...
|
112
|
-
def HasField(self, field_name: typing_extensions.Literal["value",b"value"]) -> builtins.bool: ...
|
113
|
-
def ClearField(self, field_name: typing_extensions.Literal["key",b"key","value",b"value"]) -> None: ...
|
114
|
-
|
115
|
-
FAB_ID_FIELD_NUMBER: builtins.int
|
116
|
-
FAB_VERSION_FIELD_NUMBER: builtins.int
|
117
|
-
OVERRIDE_CONFIG_FIELD_NUMBER: builtins.int
|
118
|
-
FAB_FIELD_NUMBER: builtins.int
|
119
|
-
fab_id: typing.Text
|
120
|
-
fab_version: typing.Text
|
121
|
-
@property
|
122
|
-
def override_config(self) -> google.protobuf.internal.containers.MessageMap[typing.Text, flwr.proto.transport_pb2.Scalar]: ...
|
123
|
-
@property
|
124
|
-
def fab(self) -> flwr.proto.fab_pb2.Fab: ...
|
125
|
-
def __init__(self,
|
126
|
-
*,
|
127
|
-
fab_id: typing.Text = ...,
|
128
|
-
fab_version: typing.Text = ...,
|
129
|
-
override_config: typing.Optional[typing.Mapping[typing.Text, flwr.proto.transport_pb2.Scalar]] = ...,
|
130
|
-
fab: typing.Optional[flwr.proto.fab_pb2.Fab] = ...,
|
131
|
-
) -> None: ...
|
132
|
-
def HasField(self, field_name: typing_extensions.Literal["fab",b"fab"]) -> builtins.bool: ...
|
133
|
-
def ClearField(self, field_name: typing_extensions.Literal["fab",b"fab","fab_id",b"fab_id","fab_version",b"fab_version","override_config",b"override_config"]) -> None: ...
|
134
|
-
global___CreateRunRequest = CreateRunRequest
|
135
|
-
|
136
|
-
class CreateRunResponse(google.protobuf.message.Message):
|
137
|
-
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
138
|
-
RUN_ID_FIELD_NUMBER: builtins.int
|
139
|
-
run_id: builtins.int
|
140
|
-
def __init__(self,
|
141
|
-
*,
|
142
|
-
run_id: builtins.int = ...,
|
143
|
-
) -> None: ...
|
144
|
-
def ClearField(self, field_name: typing_extensions.Literal["run_id",b"run_id"]) -> None: ...
|
145
|
-
global___CreateRunResponse = CreateRunResponse
|
146
|
-
|
147
96
|
class GetRunRequest(google.protobuf.message.Message):
|
148
97
|
"""GetRun"""
|
149
98
|
DESCRIPTOR: google.protobuf.descriptor.Descriptor
|
flwr/proto/serverappio_pb2.py
CHANGED
@@ -20,7 +20,7 @@ from flwr.proto import run_pb2 as flwr_dot_proto_dot_run__pb2
|
|
20
20
|
from flwr.proto import fab_pb2 as flwr_dot_proto_dot_fab__pb2
|
21
21
|
|
22
22
|
|
23
|
-
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/serverappio.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"T\n\x16PushInsMessagesRequest\x12*\n\rmessages_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.Message\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\".\n\x17PushInsMessagesResponse\x12\x13\n\x0bmessage_ids\x18\x01 \x03(\t\"=\n\x16PullResMessagesRequest\x12\x13\n\x0bmessage_ids\x18\x01 \x03(\t\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\"E\n\x17PullResMessagesResponse\x12*\n\rmessages_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.Message\"\x1c\n\x1aPullServerAppInputsRequest\"\x7f\n\x1bPullServerAppInputsResponse\x12$\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Fab\"S\n\x1bPushServerAppOutputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\"\x1e\n\x1cPushServerAppOutputsResponse2\
|
23
|
+
DESCRIPTOR = _descriptor_pool.Default().AddSerializedFile(b'\n\x1c\x66lwr/proto/serverappio.proto\x12\nflwr.proto\x1a\x1a\x66lwr/proto/heartbeat.proto\x1a\x14\x66lwr/proto/log.proto\x1a\x15\x66lwr/proto/node.proto\x1a\x18\x66lwr/proto/message.proto\x1a\x14\x66lwr/proto/run.proto\x1a\x14\x66lwr/proto/fab.proto\"!\n\x0fGetNodesRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\"3\n\x10GetNodesResponse\x12\x1f\n\x05nodes\x18\x01 \x03(\x0b\x32\x10.flwr.proto.Node\"T\n\x16PushInsMessagesRequest\x12*\n\rmessages_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.Message\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\".\n\x17PushInsMessagesResponse\x12\x13\n\x0bmessage_ids\x18\x01 \x03(\t\"=\n\x16PullResMessagesRequest\x12\x13\n\x0bmessage_ids\x18\x01 \x03(\t\x12\x0e\n\x06run_id\x18\x02 \x01(\x04\"E\n\x17PullResMessagesResponse\x12*\n\rmessages_list\x18\x01 \x03(\x0b\x32\x13.flwr.proto.Message\"\x1c\n\x1aPullServerAppInputsRequest\"\x7f\n\x1bPullServerAppInputsResponse\x12$\n\x07\x63ontext\x18\x01 \x01(\x0b\x32\x13.flwr.proto.Context\x12\x1c\n\x03run\x18\x02 \x01(\x0b\x32\x0f.flwr.proto.Run\x12\x1c\n\x03\x66\x61\x62\x18\x03 \x01(\x0b\x32\x0f.flwr.proto.Fab\"S\n\x1bPushServerAppOutputsRequest\x12\x0e\n\x06run_id\x18\x01 \x01(\x04\x12$\n\x07\x63ontext\x18\x02 \x01(\x0b\x32\x13.flwr.proto.Context\"\x1e\n\x1cPushServerAppOutputsResponse2\xe4\x08\n\x0bServerAppIo\x12G\n\x08GetNodes\x12\x1b.flwr.proto.GetNodesRequest\x1a\x1c.flwr.proto.GetNodesResponse\"\x00\x12Y\n\x0cPushMessages\x12\".flwr.proto.PushInsMessagesRequest\x1a#.flwr.proto.PushInsMessagesResponse\"\x00\x12Y\n\x0cPullMessages\x12\".flwr.proto.PullResMessagesRequest\x1a#.flwr.proto.PullResMessagesResponse\"\x00\x12\x41\n\x06GetRun\x12\x19.flwr.proto.GetRunRequest\x1a\x1a.flwr.proto.GetRunResponse\"\x00\x12\x41\n\x06GetFab\x12\x19.flwr.proto.GetFabRequest\x1a\x1a.flwr.proto.GetFabResponse\"\x00\x12h\n\x13PullServerAppInputs\x12&.flwr.proto.PullServerAppInputsRequest\x1a\'.flwr.proto.PullServerAppInputsResponse\"\x00\x12k\n\x14PushServerAppOutputs\x12\'.flwr.proto.PushServerAppOutputsRequest\x1a(.flwr.proto.PushServerAppOutputsResponse\"\x00\x12\\\n\x0fUpdateRunStatus\x12\".flwr.proto.UpdateRunStatusRequest\x1a#.flwr.proto.UpdateRunStatusResponse\"\x00\x12S\n\x0cGetRunStatus\x12\x1f.flwr.proto.GetRunStatusRequest\x1a .flwr.proto.GetRunStatusResponse\"\x00\x12G\n\x08PushLogs\x12\x1b.flwr.proto.PushLogsRequest\x1a\x1c.flwr.proto.PushLogsResponse\"\x00\x12_\n\x10SendAppHeartbeat\x12#.flwr.proto.SendAppHeartbeatRequest\x1a$.flwr.proto.SendAppHeartbeatResponse\"\x00\x12M\n\nPushObject\x12\x1d.flwr.proto.PushObjectRequest\x1a\x1e.flwr.proto.PushObjectResponse\"\x00\x12M\n\nPullObject\x12\x1d.flwr.proto.PullObjectRequest\x1a\x1e.flwr.proto.PullObjectResponse\"\x00\x62\x06proto3')
|
24
24
|
|
25
25
|
_globals = globals()
|
26
26
|
_builder.BuildMessageAndEnumDescriptors(DESCRIPTOR, _globals)
|
@@ -48,5 +48,5 @@ if _descriptor._USE_C_DESCRIPTORS == False:
|
|
48
48
|
_globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_start=787
|
49
49
|
_globals['_PUSHSERVERAPPOUTPUTSRESPONSE']._serialized_end=817
|
50
50
|
_globals['_SERVERAPPIO']._serialized_start=820
|
51
|
-
_globals['_SERVERAPPIO']._serialized_end=
|
51
|
+
_globals['_SERVERAPPIO']._serialized_end=1944
|
52
52
|
# @@protoc_insertion_point(module_scope)
|
@@ -19,11 +19,6 @@ class ServerAppIoStub(object):
|
|
19
19
|
Args:
|
20
20
|
channel: A grpc.Channel.
|
21
21
|
"""
|
22
|
-
self.CreateRun = channel.unary_unary(
|
23
|
-
'/flwr.proto.ServerAppIo/CreateRun',
|
24
|
-
request_serializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString,
|
25
|
-
response_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString,
|
26
|
-
)
|
27
22
|
self.GetNodes = channel.unary_unary(
|
28
23
|
'/flwr.proto.ServerAppIo/GetNodes',
|
29
24
|
request_serializer=flwr_dot_proto_dot_serverappio__pb2.GetNodesRequest.SerializeToString,
|
@@ -94,13 +89,6 @@ class ServerAppIoStub(object):
|
|
94
89
|
class ServerAppIoServicer(object):
|
95
90
|
"""Missing associated documentation comment in .proto file."""
|
96
91
|
|
97
|
-
def CreateRun(self, request, context):
|
98
|
-
"""Request run_id
|
99
|
-
"""
|
100
|
-
context.set_code(grpc.StatusCode.UNIMPLEMENTED)
|
101
|
-
context.set_details('Method not implemented!')
|
102
|
-
raise NotImplementedError('Method not implemented!')
|
103
|
-
|
104
92
|
def GetNodes(self, request, context):
|
105
93
|
"""Return a set of nodes
|
106
94
|
"""
|
@@ -195,11 +183,6 @@ class ServerAppIoServicer(object):
|
|
195
183
|
|
196
184
|
def add_ServerAppIoServicer_to_server(servicer, server):
|
197
185
|
rpc_method_handlers = {
|
198
|
-
'CreateRun': grpc.unary_unary_rpc_method_handler(
|
199
|
-
servicer.CreateRun,
|
200
|
-
request_deserializer=flwr_dot_proto_dot_run__pb2.CreateRunRequest.FromString,
|
201
|
-
response_serializer=flwr_dot_proto_dot_run__pb2.CreateRunResponse.SerializeToString,
|
202
|
-
),
|
203
186
|
'GetNodes': grpc.unary_unary_rpc_method_handler(
|
204
187
|
servicer.GetNodes,
|
205
188
|
request_deserializer=flwr_dot_proto_dot_serverappio__pb2.GetNodesRequest.FromString,
|
@@ -275,23 +258,6 @@ def add_ServerAppIoServicer_to_server(servicer, server):
|
|
275
258
|
class ServerAppIo(object):
|
276
259
|
"""Missing associated documentation comment in .proto file."""
|
277
260
|
|
278
|
-
@staticmethod
|
279
|
-
def CreateRun(request,
|
280
|
-
target,
|
281
|
-
options=(),
|
282
|
-
channel_credentials=None,
|
283
|
-
call_credentials=None,
|
284
|
-
insecure=False,
|
285
|
-
compression=None,
|
286
|
-
wait_for_ready=None,
|
287
|
-
timeout=None,
|
288
|
-
metadata=None):
|
289
|
-
return grpc.experimental.unary_unary(request, target, '/flwr.proto.ServerAppIo/CreateRun',
|
290
|
-
flwr_dot_proto_dot_run__pb2.CreateRunRequest.SerializeToString,
|
291
|
-
flwr_dot_proto_dot_run__pb2.CreateRunResponse.FromString,
|
292
|
-
options, channel_credentials,
|
293
|
-
insecure, call_credentials, compression, wait_for_ready, timeout, metadata)
|
294
|
-
|
295
261
|
@staticmethod
|
296
262
|
def GetNodes(request,
|
297
263
|
target,
|
@@ -13,11 +13,6 @@ import grpc
|
|
13
13
|
|
14
14
|
class ServerAppIoStub:
|
15
15
|
def __init__(self, channel: grpc.Channel) -> None: ...
|
16
|
-
CreateRun: grpc.UnaryUnaryMultiCallable[
|
17
|
-
flwr.proto.run_pb2.CreateRunRequest,
|
18
|
-
flwr.proto.run_pb2.CreateRunResponse]
|
19
|
-
"""Request run_id"""
|
20
|
-
|
21
16
|
GetNodes: grpc.UnaryUnaryMultiCallable[
|
22
17
|
flwr.proto.serverappio_pb2.GetNodesRequest,
|
23
18
|
flwr.proto.serverappio_pb2.GetNodesResponse]
|
@@ -85,14 +80,6 @@ class ServerAppIoStub:
|
|
85
80
|
|
86
81
|
|
87
82
|
class ServerAppIoServicer(metaclass=abc.ABCMeta):
|
88
|
-
@abc.abstractmethod
|
89
|
-
def CreateRun(self,
|
90
|
-
request: flwr.proto.run_pb2.CreateRunRequest,
|
91
|
-
context: grpc.ServicerContext,
|
92
|
-
) -> flwr.proto.run_pb2.CreateRunResponse:
|
93
|
-
"""Request run_id"""
|
94
|
-
pass
|
95
|
-
|
96
83
|
@abc.abstractmethod
|
97
84
|
def GetNodes(self,
|
98
85
|
request: flwr.proto.serverappio_pb2.GetNodesRequest,
|
flwr/server/app.py
CHANGED
@@ -71,6 +71,7 @@ from flwr.proto.grpcadapter_pb2_grpc import add_GrpcAdapterServicer_to_server
|
|
71
71
|
from flwr.server.fleet_event_log_interceptor import FleetEventLogInterceptor
|
72
72
|
from flwr.server.serverapp.app import flwr_serverapp
|
73
73
|
from flwr.simulation.app import flwr_simulation
|
74
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
74
75
|
from flwr.superexec.app import load_executor
|
75
76
|
from flwr.superexec.exec_grpc import run_exec_api_grpc
|
76
77
|
|
@@ -307,6 +308,9 @@ def run_superlink() -> None:
|
|
307
308
|
# Initialize FfsFactory
|
308
309
|
ffs_factory = FfsFactory(args.storage_dir)
|
309
310
|
|
311
|
+
# Initialize ObjectStoreFactory
|
312
|
+
objectstore_factory = ObjectStoreFactory()
|
313
|
+
|
310
314
|
# Start Exec API
|
311
315
|
executor = load_executor(args)
|
312
316
|
exec_server: grpc.Server = run_exec_api_grpc(
|
@@ -343,6 +347,7 @@ def run_superlink() -> None:
|
|
343
347
|
address=serverappio_address,
|
344
348
|
state_factory=state_factory,
|
345
349
|
ffs_factory=ffs_factory,
|
350
|
+
objectstore_factory=objectstore_factory,
|
346
351
|
certificates=None, # ServerAppIo API doesn't support SSL yet
|
347
352
|
)
|
348
353
|
grpc_servers.append(serverappio_server)
|
@@ -421,6 +426,7 @@ def run_superlink() -> None:
|
|
421
426
|
address=fleet_address,
|
422
427
|
state_factory=state_factory,
|
423
428
|
ffs_factory=ffs_factory,
|
429
|
+
objectstore_factory=objectstore_factory,
|
424
430
|
certificates=certificates,
|
425
431
|
interceptors=interceptors,
|
426
432
|
)
|
@@ -430,6 +436,7 @@ def run_superlink() -> None:
|
|
430
436
|
address=fleet_address,
|
431
437
|
state_factory=state_factory,
|
432
438
|
ffs_factory=ffs_factory,
|
439
|
+
objectstore_factory=objectstore_factory,
|
433
440
|
certificates=certificates,
|
434
441
|
)
|
435
442
|
grpc_servers.append(fleet_server)
|
@@ -668,10 +675,11 @@ def _try_obtain_fleet_event_log_writer_plugin() -> Optional[EventLogWriterPlugin
|
|
668
675
|
sys.exit("No Fleet API event log writer plugins are currently supported.")
|
669
676
|
|
670
677
|
|
671
|
-
def _run_fleet_api_grpc_rere(
|
678
|
+
def _run_fleet_api_grpc_rere( # pylint: disable=R0913, R0917
|
672
679
|
address: str,
|
673
680
|
state_factory: LinkStateFactory,
|
674
681
|
ffs_factory: FfsFactory,
|
682
|
+
objectstore_factory: ObjectStoreFactory,
|
675
683
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
676
684
|
interceptors: Optional[Sequence[grpc.ServerInterceptor]] = None,
|
677
685
|
) -> grpc.Server:
|
@@ -680,6 +688,7 @@ def _run_fleet_api_grpc_rere(
|
|
680
688
|
fleet_servicer = FleetServicer(
|
681
689
|
state_factory=state_factory,
|
682
690
|
ffs_factory=ffs_factory,
|
691
|
+
objectstore_factory=objectstore_factory,
|
683
692
|
)
|
684
693
|
fleet_add_servicer_to_server_fn = add_FleetServicer_to_server
|
685
694
|
fleet_grpc_server = generic_create_grpc_server(
|
@@ -700,6 +709,7 @@ def _run_fleet_api_grpc_adapter(
|
|
700
709
|
address: str,
|
701
710
|
state_factory: LinkStateFactory,
|
702
711
|
ffs_factory: FfsFactory,
|
712
|
+
objectstore_factory: ObjectStoreFactory,
|
703
713
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
704
714
|
) -> grpc.Server:
|
705
715
|
"""Run Fleet API (GrpcAdapter)."""
|
@@ -707,6 +717,7 @@ def _run_fleet_api_grpc_adapter(
|
|
707
717
|
fleet_servicer = GrpcAdapterServicer(
|
708
718
|
state_factory=state_factory,
|
709
719
|
ffs_factory=ffs_factory,
|
720
|
+
objectstore_factory=objectstore_factory,
|
710
721
|
)
|
711
722
|
fleet_add_servicer_to_server_fn = add_GrpcAdapterServicer_to_server
|
712
723
|
fleet_grpc_server = generic_create_grpc_server(
|
@@ -50,16 +50,21 @@ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
50
50
|
from flwr.server.superlink.fleet.message_handler import message_handler
|
51
51
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
52
52
|
from flwr.server.superlink.utils import abort_grpc_context
|
53
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
53
54
|
|
54
55
|
|
55
56
|
class FleetServicer(fleet_pb2_grpc.FleetServicer):
|
56
57
|
"""Fleet API servicer."""
|
57
58
|
|
58
59
|
def __init__(
|
59
|
-
self,
|
60
|
+
self,
|
61
|
+
state_factory: LinkStateFactory,
|
62
|
+
ffs_factory: FfsFactory,
|
63
|
+
objectstore_factory: ObjectStoreFactory,
|
60
64
|
) -> None:
|
61
65
|
self.state_factory = state_factory
|
62
66
|
self.ffs_factory = ffs_factory
|
67
|
+
self.objectstore_factory = objectstore_factory
|
63
68
|
|
64
69
|
def CreateNode(
|
65
70
|
self, request: CreateNodeRequest, context: grpc.ServicerContext
|
@@ -28,6 +28,7 @@ from flwr.proto.serverappio_pb2_grpc import ( # pylint: disable=E0611
|
|
28
28
|
)
|
29
29
|
from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
30
30
|
from flwr.server.superlink.linkstate import LinkStateFactory
|
31
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
31
32
|
|
32
33
|
from .serverappio_servicer import ServerAppIoServicer
|
33
34
|
|
@@ -36,6 +37,7 @@ def run_serverappio_api_grpc(
|
|
36
37
|
address: str,
|
37
38
|
state_factory: LinkStateFactory,
|
38
39
|
ffs_factory: FfsFactory,
|
40
|
+
objectstore_factory: ObjectStoreFactory,
|
39
41
|
certificates: Optional[tuple[bytes, bytes, bytes]],
|
40
42
|
) -> grpc.Server:
|
41
43
|
"""Run ServerAppIo API (gRPC, request-response)."""
|
@@ -43,6 +45,7 @@ def run_serverappio_api_grpc(
|
|
43
45
|
serverappio_servicer: grpc.Server = ServerAppIoServicer(
|
44
46
|
state_factory=state_factory,
|
45
47
|
ffs_factory=ffs_factory,
|
48
|
+
objectstore_factory=objectstore_factory,
|
46
49
|
)
|
47
50
|
serverappio_add_servicer_to_server_fn = add_ServerAppIoServicer_to_server
|
48
51
|
serverappio_grpc_server = generic_create_grpc_server(
|
@@ -22,21 +22,19 @@ from uuid import UUID
|
|
22
22
|
|
23
23
|
import grpc
|
24
24
|
|
25
|
-
from flwr.common import
|
25
|
+
from flwr.common import Message
|
26
26
|
from flwr.common.constant import SUPERLINK_NODE_ID, Status
|
27
27
|
from flwr.common.inflatable import check_body_len_consistency
|
28
28
|
from flwr.common.logger import log
|
29
29
|
from flwr.common.serde import (
|
30
30
|
context_from_proto,
|
31
31
|
context_to_proto,
|
32
|
-
fab_from_proto,
|
33
32
|
fab_to_proto,
|
34
33
|
message_from_proto,
|
35
34
|
message_to_proto,
|
36
35
|
run_status_from_proto,
|
37
36
|
run_status_to_proto,
|
38
37
|
run_to_proto,
|
39
|
-
user_config_from_proto,
|
40
38
|
)
|
41
39
|
from flwr.common.typing import Fab, RunStatus
|
42
40
|
from flwr.proto import serverappio_pb2_grpc # pylint: disable=E0611
|
@@ -57,8 +55,6 @@ from flwr.proto.message_pb2 import ( # pylint: disable=E0611
|
|
57
55
|
)
|
58
56
|
from flwr.proto.node_pb2 import Node # pylint: disable=E0611
|
59
57
|
from flwr.proto.run_pb2 import ( # pylint: disable=E0611
|
60
|
-
CreateRunRequest,
|
61
|
-
CreateRunResponse,
|
62
58
|
GetRunRequest,
|
63
59
|
GetRunResponse,
|
64
60
|
GetRunStatusRequest,
|
@@ -83,16 +79,21 @@ from flwr.server.superlink.ffs.ffs_factory import FfsFactory
|
|
83
79
|
from flwr.server.superlink.linkstate import LinkState, LinkStateFactory
|
84
80
|
from flwr.server.superlink.utils import abort_if
|
85
81
|
from flwr.server.utils.validator import validate_message
|
82
|
+
from flwr.supercore.object_store import ObjectStoreFactory
|
86
83
|
|
87
84
|
|
88
85
|
class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
|
89
86
|
"""ServerAppIo API servicer."""
|
90
87
|
|
91
88
|
def __init__(
|
92
|
-
self,
|
89
|
+
self,
|
90
|
+
state_factory: LinkStateFactory,
|
91
|
+
ffs_factory: FfsFactory,
|
92
|
+
objectstore_factory: ObjectStoreFactory,
|
93
93
|
) -> None:
|
94
94
|
self.state_factory = state_factory
|
95
95
|
self.ffs_factory = ffs_factory
|
96
|
+
self.objectstore_factory = objectstore_factory
|
96
97
|
self.lock = threading.RLock()
|
97
98
|
|
98
99
|
def GetNodes(
|
@@ -116,32 +117,6 @@ class ServerAppIoServicer(serverappio_pb2_grpc.ServerAppIoServicer):
|
|
116
117
|
nodes: list[Node] = [Node(node_id=node_id) for node_id in all_ids]
|
117
118
|
return GetNodesResponse(nodes=nodes)
|
118
119
|
|
119
|
-
def CreateRun(
|
120
|
-
self, request: CreateRunRequest, context: grpc.ServicerContext
|
121
|
-
) -> CreateRunResponse:
|
122
|
-
"""Create run ID."""
|
123
|
-
log(DEBUG, "ServerAppIoServicer.CreateRun")
|
124
|
-
state: LinkState = self.state_factory.state()
|
125
|
-
if request.HasField("fab"):
|
126
|
-
fab = fab_from_proto(request.fab)
|
127
|
-
ffs: Ffs = self.ffs_factory.ffs()
|
128
|
-
fab_hash = ffs.put(fab.content, {})
|
129
|
-
_raise_if(
|
130
|
-
validation_error=fab_hash != fab.hash_str,
|
131
|
-
request_name="CreateRun",
|
132
|
-
detail=f"FAB ({fab.hash_str}) hash from request doesn't match contents",
|
133
|
-
)
|
134
|
-
else:
|
135
|
-
fab_hash = ""
|
136
|
-
run_id = state.create_run(
|
137
|
-
request.fab_id,
|
138
|
-
request.fab_version,
|
139
|
-
fab_hash,
|
140
|
-
user_config_from_proto(request.override_config),
|
141
|
-
ConfigRecord(),
|
142
|
-
)
|
143
|
-
return CreateRunResponse(run_id=run_id)
|
144
|
-
|
145
120
|
def PushMessages(
|
146
121
|
self, request: PushInsMessagesRequest, context: grpc.ServicerContext
|
147
122
|
) -> PushInsMessagesResponse:
|
@@ -0,0 +1,23 @@
|
|
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
|
+
"""Flower ObjectStore."""
|
16
|
+
|
17
|
+
from .object_store import ObjectStore
|
18
|
+
from .object_store_factory import ObjectStoreFactory
|
19
|
+
|
20
|
+
__all__ = [
|
21
|
+
"ObjectStore",
|
22
|
+
"ObjectStoreFactory",
|
23
|
+
]
|