flwr 1.25.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 +18 -69
- 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 +28 -58
- flwr/cli/new/new.py +9 -29
- flwr/cli/pull.py +19 -37
- flwr/cli/run/run.py +85 -93
- flwr/cli/run_utils.py +1 -1
- flwr/cli/stop.py +32 -73
- flwr/cli/supernode/ls.py +25 -57
- 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 -275
- flwr/client/grpc_rere_client/connection.py +3 -3
- 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/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 +7 -5
- flwr/common/telemetry.py +1 -1
- flwr/common/typing.py +4 -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/run_pb2.py +5 -13
- flwr/proto/run_pb2.pyi +0 -57
- 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 +6 -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 +6 -5
- flwr/server/superlink/linkstate/__init__.py +2 -2
- flwr/server/superlink/linkstate/in_memory_linkstate.py +2 -10
- flwr/server/superlink/linkstate/linkstate.py +2 -21
- flwr/server/superlink/linkstate/linkstate_factory.py +16 -8
- flwr/server/superlink/linkstate/{sqlite_linkstate.py → sql_linkstate.py} +432 -534
- 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 +24 -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/supercore/state/__init__.py +15 -0
- flwr/supercore/state/alembic/__init__.py +15 -0
- 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/supercore/state/alembic/versions/__init__.py +15 -0
- 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/supercore/state/schema/__init__.py +15 -0
- 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 +36 -1
- flwr/superlink/federation/federation_manager.py +2 -2
- flwr/superlink/federation/noop_federation_manager.py +8 -6
- flwr/superlink/servicer/control/control_servicer.py +19 -17
- flwr/supernode/cli/flower_supernode.py +2 -1
- flwr/supernode/runtime/run_clientapp.py +14 -14
- flwr/supernode/servicer/clientappio/clientappio_servicer.py +10 -8
- flwr/supernode/start_client_internal.py +10 -6
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/METADATA +7 -5
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/RECORD +137 -116
- flwr/cli/federation/show.py +0 -318
- flwr/common/pyproject.py +0 -42
- flwr/supercore/sqlite_mixin.py +0 -159
- /flwr/{common → supercore}/version.py +0 -0
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/WHEEL +0 -0
- {flwr-1.25.0.dist-info → flwr-1.26.0.dist-info}/entry_points.txt +0 -0
|
@@ -0,0 +1,239 @@
|
|
|
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
|
+
"""Helpers for running and validating Alembic migrations."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from logging import INFO
|
|
19
|
+
from pathlib import Path
|
|
20
|
+
|
|
21
|
+
from alembic import command
|
|
22
|
+
from alembic.config import Config
|
|
23
|
+
from sqlalchemy import MetaData, create_engine, inspect, pool
|
|
24
|
+
from sqlalchemy.engine import Engine
|
|
25
|
+
|
|
26
|
+
from flwr.common.exit import ExitCode, flwr_exit
|
|
27
|
+
from flwr.common.logger import log
|
|
28
|
+
from flwr.supercore.state.schema.corestate_tables import create_corestate_metadata
|
|
29
|
+
from flwr.supercore.state.schema.linkstate_tables import create_linkstate_metadata
|
|
30
|
+
from flwr.supercore.state.schema.objectstore_tables import create_objectstore_metadata
|
|
31
|
+
|
|
32
|
+
ALEMBIC_DIR = Path(__file__).resolve().parent
|
|
33
|
+
ALEMBIC_VERSION_TABLE = "alembic_version"
|
|
34
|
+
FLWR_STATE_BASELINE_REVISION = "8e65d8ae60b0"
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def get_combined_metadata() -> MetaData:
|
|
38
|
+
"""Combine all Flower state metadata objects into a single MetaData instance.
|
|
39
|
+
|
|
40
|
+
This ensures Alembic can track all tables across CoreState, LinkState, and
|
|
41
|
+
ObjectStore.
|
|
42
|
+
|
|
43
|
+
Returns
|
|
44
|
+
-------
|
|
45
|
+
MetaData
|
|
46
|
+
Combined SQLAlchemy MetaData with all Flower state tables.
|
|
47
|
+
"""
|
|
48
|
+
# Start with linkstate tables
|
|
49
|
+
metadata = create_linkstate_metadata()
|
|
50
|
+
|
|
51
|
+
# Add corestate tables
|
|
52
|
+
corestate_metadata = create_corestate_metadata()
|
|
53
|
+
for table in corestate_metadata.tables.values():
|
|
54
|
+
table.to_metadata(metadata)
|
|
55
|
+
|
|
56
|
+
# Add objectstore tables
|
|
57
|
+
objectstore_metadata = create_objectstore_metadata()
|
|
58
|
+
for table in objectstore_metadata.tables.values():
|
|
59
|
+
table.to_metadata(metadata)
|
|
60
|
+
|
|
61
|
+
return metadata
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
def run_migrations(engine: Engine) -> None:
|
|
65
|
+
"""Run pending Alembic migrations, handling pre-Alembic legacy databases.
|
|
66
|
+
|
|
67
|
+
Expected scenarios:
|
|
68
|
+
- If alembic_version exists: run migrations normally.
|
|
69
|
+
- If DB is empty, e.g. when newly created: run migrations normally.
|
|
70
|
+
- If DB is pre-Alembic and schema is mismatched: exit with guidance.
|
|
71
|
+
- If DB is pre-Alembic and schema matches baseline: stamp, then upgrade.
|
|
72
|
+
"""
|
|
73
|
+
config = build_alembic_config(engine)
|
|
74
|
+
has_version_table = _has_alembic_version_table(engine)
|
|
75
|
+
|
|
76
|
+
# Standard database with version tracking: just upgrade.
|
|
77
|
+
if has_version_table:
|
|
78
|
+
command.upgrade(config, "head")
|
|
79
|
+
return
|
|
80
|
+
|
|
81
|
+
table_names = _get_user_table_names(engine)
|
|
82
|
+
|
|
83
|
+
# Empty/new database: run all migrations from scratch.
|
|
84
|
+
if not table_names:
|
|
85
|
+
command.upgrade(config, "head")
|
|
86
|
+
return
|
|
87
|
+
|
|
88
|
+
# Pre-Alembic database detected without version tracking: verify database matches
|
|
89
|
+
# baseline schema before stamping version and upgrading.
|
|
90
|
+
is_valid, error_msg = _verify_legacy_schema_matches_baseline(engine)
|
|
91
|
+
|
|
92
|
+
# This is an edge case and unlikely to happen since SuperLink requires a specific
|
|
93
|
+
# schema to operate normally.
|
|
94
|
+
if not is_valid:
|
|
95
|
+
flwr_exit(
|
|
96
|
+
ExitCode.SUPERLINK_DATABASE_SCHEMA_MISMATCH,
|
|
97
|
+
"Detected a pre-Alembic Flower state database, but its schema does not "
|
|
98
|
+
f"match the baseline migration (revision {FLWR_STATE_BASELINE_REVISION}). "
|
|
99
|
+
"Back up the database and either migrate it manually to the baseline "
|
|
100
|
+
"schema or start with a fresh database. "
|
|
101
|
+
f"{error_msg}",
|
|
102
|
+
)
|
|
103
|
+
|
|
104
|
+
log(
|
|
105
|
+
INFO,
|
|
106
|
+
"Detected pre-Alembic state database without alembic_version; stamping to %s "
|
|
107
|
+
"before upgrading.",
|
|
108
|
+
FLWR_STATE_BASELINE_REVISION,
|
|
109
|
+
)
|
|
110
|
+
stamp_existing_database(engine, FLWR_STATE_BASELINE_REVISION)
|
|
111
|
+
command.upgrade(config, "head")
|
|
112
|
+
log(INFO, "Flower state database stamped and upgraded successfully!")
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def build_alembic_config(engine: Engine) -> Config:
|
|
116
|
+
"""Create Alembic config with script location and DB URL."""
|
|
117
|
+
config = Config()
|
|
118
|
+
config.set_main_option("script_location", str(ALEMBIC_DIR))
|
|
119
|
+
config.set_main_option("sqlalchemy.url", str(engine.url))
|
|
120
|
+
return config
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def _get_user_table_names(engine: Engine) -> set[str]:
|
|
124
|
+
"""Return non-internal table names for the given engine."""
|
|
125
|
+
inspector = inspect(engine)
|
|
126
|
+
table_names = set(inspector.get_table_names())
|
|
127
|
+
# Exclude SQLite internal tables (for example, sqlite_sequence)
|
|
128
|
+
return {name for name in table_names if not name.startswith("sqlite_")}
|
|
129
|
+
|
|
130
|
+
|
|
131
|
+
def _has_alembic_version_table(engine: Engine) -> bool:
|
|
132
|
+
"""Return True if the Alembic version table exists."""
|
|
133
|
+
inspector = inspect(engine)
|
|
134
|
+
return inspector.has_table(ALEMBIC_VERSION_TABLE)
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
def _get_baseline_metadata() -> MetaData:
|
|
138
|
+
"""Create an in-memory DB at baseline revision and reflect its schema.
|
|
139
|
+
|
|
140
|
+
Uses an in-memory SQLite database instead of a temporary file to avoid requiring
|
|
141
|
+
filesystem write access. Note that this function is only invoked for pre-Alembic
|
|
142
|
+
databases.
|
|
143
|
+
|
|
144
|
+
The implementation uses StaticPool and passes an active connection via
|
|
145
|
+
config.attributes to Alembic's env.py. This ensures the same in-memory database
|
|
146
|
+
instance is used throughout migration and reflection, since each new connection to
|
|
147
|
+
sqlite:///:memory: creates a separate empty database.
|
|
148
|
+
"""
|
|
149
|
+
# Create an in-memory SQLite database with StaticPool to ensure connection reuse.
|
|
150
|
+
# This is needed because in-memory databases are instance-specific per connection.
|
|
151
|
+
engine = create_engine(
|
|
152
|
+
"sqlite:///:memory:",
|
|
153
|
+
connect_args={"check_same_thread": False},
|
|
154
|
+
poolclass=pool.StaticPool,
|
|
155
|
+
)
|
|
156
|
+
try:
|
|
157
|
+
# Open a connection and pass it to Alembic to ensure the in-memory database
|
|
158
|
+
# persists throughout the migration process. Without this, Alembic would
|
|
159
|
+
# create a new connection (and thus a new empty database) from the URL.
|
|
160
|
+
with engine.begin() as connection:
|
|
161
|
+
config = build_alembic_config(engine)
|
|
162
|
+
# Store the connection in config.attributes so env.py can use it directly.
|
|
163
|
+
# This prevents Alembic from creating a new connection and losing our data.
|
|
164
|
+
config.attributes["connection"] = connection
|
|
165
|
+
command.upgrade(config, FLWR_STATE_BASELINE_REVISION)
|
|
166
|
+
|
|
167
|
+
# Reflect the baseline schema from the in-memory database.
|
|
168
|
+
# At this point, the StaticPool ensures we're still connected to the same
|
|
169
|
+
# database instance that contains the migrated tables.
|
|
170
|
+
baseline_metadata = MetaData()
|
|
171
|
+
baseline_metadata.reflect(
|
|
172
|
+
bind=engine,
|
|
173
|
+
only=lambda table_name, _: table_name != ALEMBIC_VERSION_TABLE,
|
|
174
|
+
)
|
|
175
|
+
finally:
|
|
176
|
+
engine.dispose()
|
|
177
|
+
|
|
178
|
+
return baseline_metadata
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
def _verify_legacy_schema_matches_baseline(engine: Engine) -> tuple[bool, str]:
|
|
182
|
+
"""Verify legacy schema matches baseline tables and columns.
|
|
183
|
+
|
|
184
|
+
Only missing tables/columns are reported as errors.
|
|
185
|
+
|
|
186
|
+
Returns
|
|
187
|
+
-------
|
|
188
|
+
tuple[bool, str]
|
|
189
|
+
(is_valid, error_message). If valid, error_message is empty.
|
|
190
|
+
"""
|
|
191
|
+
inspector = inspect(engine)
|
|
192
|
+
|
|
193
|
+
# Get the baseline schema by running migrations up to the baseline revision
|
|
194
|
+
# in a temporary database and reflecting its schema
|
|
195
|
+
baseline_metadata = _get_baseline_metadata()
|
|
196
|
+
existing_tables = set(inspector.get_table_names())
|
|
197
|
+
|
|
198
|
+
# Filter out SQLite internal tables and alembic_version
|
|
199
|
+
existing_tables = {
|
|
200
|
+
t
|
|
201
|
+
for t in existing_tables
|
|
202
|
+
if not t.startswith("sqlite_") and t != ALEMBIC_VERSION_TABLE
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
# Exclude alembic_version from baseline comparison
|
|
206
|
+
expected_tables = {
|
|
207
|
+
t for t in baseline_metadata.tables.keys() if t != ALEMBIC_VERSION_TABLE
|
|
208
|
+
}
|
|
209
|
+
missing_tables = expected_tables - existing_tables
|
|
210
|
+
|
|
211
|
+
if missing_tables:
|
|
212
|
+
table_list = ", ".join(sorted(existing_tables))
|
|
213
|
+
missing_str = ", ".join(sorted(missing_tables))
|
|
214
|
+
return False, (
|
|
215
|
+
f"Detected tables: [{table_list}]. "
|
|
216
|
+
f"Missing baseline tables: [{missing_str}]."
|
|
217
|
+
)
|
|
218
|
+
|
|
219
|
+
# Verify columns for each expected table
|
|
220
|
+
for table_name in expected_tables:
|
|
221
|
+
table = baseline_metadata.tables[table_name]
|
|
222
|
+
expected_columns = {col.name for col in table.columns}
|
|
223
|
+
actual_columns = {col["name"] for col in inspector.get_columns(table_name)}
|
|
224
|
+
|
|
225
|
+
missing_cols = expected_columns - actual_columns
|
|
226
|
+
if missing_cols:
|
|
227
|
+
missing_cols_str = ", ".join(sorted(missing_cols))
|
|
228
|
+
return False, (
|
|
229
|
+
f"Table '{table_name}' missing columns: [{missing_cols_str}]."
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
return True, ""
|
|
233
|
+
|
|
234
|
+
|
|
235
|
+
def stamp_existing_database(
|
|
236
|
+
engine: Engine, revision: str = FLWR_STATE_BASELINE_REVISION
|
|
237
|
+
) -> None:
|
|
238
|
+
"""Stamp an existing legacy database to the baseline Alembic revision."""
|
|
239
|
+
command.stamp(build_alembic_config(engine), revision)
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
"""Alembic migration versions."""
|
|
@@ -0,0 +1,200 @@
|
|
|
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
|
+
"""Initialize migration of state tables.
|
|
16
|
+
|
|
17
|
+
Revision ID: 8e65d8ae60b0
|
|
18
|
+
Revises:
|
|
19
|
+
Create Date: 2026-01-28 11:03:18.038794
|
|
20
|
+
"""
|
|
21
|
+
from collections.abc import Sequence
|
|
22
|
+
|
|
23
|
+
import sqlalchemy as sa
|
|
24
|
+
from alembic import op
|
|
25
|
+
|
|
26
|
+
# pylint: disable=no-member
|
|
27
|
+
|
|
28
|
+
# revision identifiers, used by Alembic.
|
|
29
|
+
revision: str = "8e65d8ae60b0"
|
|
30
|
+
down_revision: str | Sequence[str] | None = None
|
|
31
|
+
branch_labels: str | Sequence[str] | None = None
|
|
32
|
+
depends_on: str | Sequence[str] | None = None
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def upgrade() -> None:
|
|
36
|
+
"""Upgrade schema."""
|
|
37
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
38
|
+
# LinkState tables
|
|
39
|
+
op.create_table(
|
|
40
|
+
"node",
|
|
41
|
+
sa.Column("node_id", sa.Integer(), nullable=True),
|
|
42
|
+
sa.Column("owner_aid", sa.String(), nullable=True),
|
|
43
|
+
sa.Column("owner_name", sa.String(), nullable=True),
|
|
44
|
+
sa.Column("status", sa.String(), nullable=True),
|
|
45
|
+
sa.Column("registered_at", sa.String(), nullable=True),
|
|
46
|
+
sa.Column("last_activated_at", sa.String(), nullable=True),
|
|
47
|
+
sa.Column("last_deactivated_at", sa.String(), nullable=True),
|
|
48
|
+
sa.Column("unregistered_at", sa.String(), nullable=True),
|
|
49
|
+
sa.Column("online_until", sa.TIMESTAMP(), nullable=True),
|
|
50
|
+
sa.Column("heartbeat_interval", sa.Float(), nullable=True),
|
|
51
|
+
sa.Column("public_key", sa.LargeBinary(), nullable=True),
|
|
52
|
+
sa.UniqueConstraint("node_id"),
|
|
53
|
+
sa.UniqueConstraint("public_key"),
|
|
54
|
+
)
|
|
55
|
+
op.create_index("idx_node_owner_aid", "node", ["owner_aid"], unique=False)
|
|
56
|
+
op.create_index("idx_node_status", "node", ["status"], unique=False)
|
|
57
|
+
op.create_index("idx_online_until", "node", ["online_until"], unique=False)
|
|
58
|
+
op.create_table(
|
|
59
|
+
"run",
|
|
60
|
+
sa.Column("run_id", sa.Integer(), nullable=True),
|
|
61
|
+
sa.Column("fab_id", sa.String(), nullable=True),
|
|
62
|
+
sa.Column("fab_version", sa.String(), nullable=True),
|
|
63
|
+
sa.Column("fab_hash", sa.String(), nullable=True),
|
|
64
|
+
sa.Column("override_config", sa.String(), nullable=True),
|
|
65
|
+
sa.Column("pending_at", sa.String(), nullable=True),
|
|
66
|
+
sa.Column("starting_at", sa.String(), nullable=True),
|
|
67
|
+
sa.Column("running_at", sa.String(), nullable=True),
|
|
68
|
+
sa.Column("finished_at", sa.String(), nullable=True),
|
|
69
|
+
sa.Column("sub_status", sa.String(), nullable=True),
|
|
70
|
+
sa.Column("details", sa.String(), nullable=True),
|
|
71
|
+
sa.Column("federation", sa.String(), nullable=True),
|
|
72
|
+
sa.Column("federation_options", sa.LargeBinary(), nullable=True),
|
|
73
|
+
sa.Column("flwr_aid", sa.String(), nullable=True),
|
|
74
|
+
sa.Column("bytes_sent", sa.Integer(), server_default="0", nullable=True),
|
|
75
|
+
sa.Column("bytes_recv", sa.Integer(), server_default="0", nullable=True),
|
|
76
|
+
sa.Column("clientapp_runtime", sa.Float(), server_default="0.0", nullable=True),
|
|
77
|
+
sa.UniqueConstraint("run_id"),
|
|
78
|
+
)
|
|
79
|
+
op.create_table(
|
|
80
|
+
"logs",
|
|
81
|
+
sa.Column("timestamp", sa.Float(), nullable=True),
|
|
82
|
+
sa.Column("run_id", sa.Integer(), nullable=True),
|
|
83
|
+
sa.Column("node_id", sa.Integer(), nullable=True),
|
|
84
|
+
sa.Column("log", sa.String(), nullable=True),
|
|
85
|
+
sa.ForeignKeyConstraint(
|
|
86
|
+
["run_id"],
|
|
87
|
+
["run.run_id"],
|
|
88
|
+
),
|
|
89
|
+
sa.UniqueConstraint("timestamp", "run_id", "node_id"),
|
|
90
|
+
)
|
|
91
|
+
op.create_table(
|
|
92
|
+
"context",
|
|
93
|
+
sa.Column("run_id", sa.Integer(), nullable=True),
|
|
94
|
+
sa.Column("context", sa.LargeBinary(), nullable=True),
|
|
95
|
+
sa.ForeignKeyConstraint(
|
|
96
|
+
["run_id"],
|
|
97
|
+
["run.run_id"],
|
|
98
|
+
),
|
|
99
|
+
sa.UniqueConstraint("run_id"),
|
|
100
|
+
)
|
|
101
|
+
op.create_table(
|
|
102
|
+
"message_ins",
|
|
103
|
+
sa.Column("message_id", sa.String(), nullable=True),
|
|
104
|
+
sa.Column("group_id", sa.String(), nullable=True),
|
|
105
|
+
sa.Column("run_id", sa.Integer(), nullable=True),
|
|
106
|
+
sa.Column("src_node_id", sa.Integer(), nullable=True),
|
|
107
|
+
sa.Column("dst_node_id", sa.Integer(), nullable=True),
|
|
108
|
+
sa.Column("reply_to_message_id", sa.String(), nullable=True),
|
|
109
|
+
sa.Column("created_at", sa.Float(), nullable=True),
|
|
110
|
+
sa.Column("delivered_at", sa.String(), nullable=True),
|
|
111
|
+
sa.Column("ttl", sa.Float(), nullable=True),
|
|
112
|
+
sa.Column("message_type", sa.String(), nullable=True),
|
|
113
|
+
sa.Column("content", sa.LargeBinary(), nullable=True),
|
|
114
|
+
sa.Column("error", sa.LargeBinary(), nullable=True),
|
|
115
|
+
sa.ForeignKeyConstraint(
|
|
116
|
+
["run_id"],
|
|
117
|
+
["run.run_id"],
|
|
118
|
+
),
|
|
119
|
+
sa.UniqueConstraint("message_id"),
|
|
120
|
+
)
|
|
121
|
+
op.create_table(
|
|
122
|
+
"message_res",
|
|
123
|
+
sa.Column("message_id", sa.String(), nullable=True),
|
|
124
|
+
sa.Column("group_id", sa.String(), nullable=True),
|
|
125
|
+
sa.Column("run_id", sa.Integer(), nullable=True),
|
|
126
|
+
sa.Column("src_node_id", sa.Integer(), nullable=True),
|
|
127
|
+
sa.Column("dst_node_id", sa.Integer(), nullable=True),
|
|
128
|
+
sa.Column("reply_to_message_id", sa.String(), nullable=True),
|
|
129
|
+
sa.Column("created_at", sa.Float(), nullable=True),
|
|
130
|
+
sa.Column("delivered_at", sa.String(), nullable=True),
|
|
131
|
+
sa.Column("ttl", sa.Float(), nullable=True),
|
|
132
|
+
sa.Column("message_type", sa.String(), nullable=True),
|
|
133
|
+
sa.Column("content", sa.LargeBinary(), nullable=True),
|
|
134
|
+
sa.Column("error", sa.LargeBinary(), nullable=True),
|
|
135
|
+
sa.ForeignKeyConstraint(
|
|
136
|
+
["run_id"],
|
|
137
|
+
["run.run_id"],
|
|
138
|
+
),
|
|
139
|
+
sa.UniqueConstraint("message_id"),
|
|
140
|
+
)
|
|
141
|
+
# CoreState tables
|
|
142
|
+
op.create_table(
|
|
143
|
+
"token_store",
|
|
144
|
+
sa.Column("run_id", sa.Integer(), nullable=True),
|
|
145
|
+
sa.Column("token", sa.String(), nullable=False),
|
|
146
|
+
sa.Column("active_until", sa.Float(), nullable=True),
|
|
147
|
+
sa.PrimaryKeyConstraint("run_id"),
|
|
148
|
+
sa.UniqueConstraint("token"),
|
|
149
|
+
)
|
|
150
|
+
# ObjectStore tables
|
|
151
|
+
op.create_table(
|
|
152
|
+
"objects",
|
|
153
|
+
sa.Column("object_id", sa.String(), nullable=True),
|
|
154
|
+
sa.Column("content", sa.LargeBinary(), nullable=True),
|
|
155
|
+
sa.Column("is_available", sa.Integer(), server_default="0", nullable=False),
|
|
156
|
+
sa.Column("ref_count", sa.Integer(), server_default="0", nullable=False),
|
|
157
|
+
sa.CheckConstraint("is_available IN (0, 1)", name="ck_objects_is_available"),
|
|
158
|
+
sa.PrimaryKeyConstraint("object_id"),
|
|
159
|
+
)
|
|
160
|
+
op.create_table(
|
|
161
|
+
"object_children",
|
|
162
|
+
sa.Column("parent_id", sa.String(), nullable=False),
|
|
163
|
+
sa.Column("child_id", sa.String(), nullable=False),
|
|
164
|
+
sa.ForeignKeyConstraint(
|
|
165
|
+
["child_id"], ["objects.object_id"], ondelete="CASCADE"
|
|
166
|
+
),
|
|
167
|
+
sa.ForeignKeyConstraint(
|
|
168
|
+
["parent_id"], ["objects.object_id"], ondelete="CASCADE"
|
|
169
|
+
),
|
|
170
|
+
sa.PrimaryKeyConstraint("parent_id", "child_id"),
|
|
171
|
+
)
|
|
172
|
+
op.create_table(
|
|
173
|
+
"run_objects",
|
|
174
|
+
sa.Column("run_id", sa.Integer(), nullable=False),
|
|
175
|
+
sa.Column("object_id", sa.String(), nullable=False),
|
|
176
|
+
sa.ForeignKeyConstraint(
|
|
177
|
+
["object_id"], ["objects.object_id"], ondelete="CASCADE"
|
|
178
|
+
),
|
|
179
|
+
sa.PrimaryKeyConstraint("run_id", "object_id"),
|
|
180
|
+
)
|
|
181
|
+
# ### end Alembic commands ###
|
|
182
|
+
|
|
183
|
+
|
|
184
|
+
def downgrade() -> None:
|
|
185
|
+
"""Downgrade schema."""
|
|
186
|
+
# ### commands auto generated by Alembic - please adjust! ###
|
|
187
|
+
op.drop_table("run_objects")
|
|
188
|
+
op.drop_table("object_children")
|
|
189
|
+
op.drop_table("objects")
|
|
190
|
+
op.drop_table("token_store")
|
|
191
|
+
op.drop_table("message_res")
|
|
192
|
+
op.drop_table("message_ins")
|
|
193
|
+
op.drop_table("context")
|
|
194
|
+
op.drop_table("logs")
|
|
195
|
+
op.drop_table("run")
|
|
196
|
+
op.drop_index("idx_online_until", table_name="node")
|
|
197
|
+
op.drop_index("idx_node_status", table_name="node")
|
|
198
|
+
op.drop_index("idx_node_owner_aid", table_name="node")
|
|
199
|
+
op.drop_table("node")
|
|
200
|
+
# ### end Alembic commands ###
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
# State Entity Relationship Diagram
|
|
2
|
+
|
|
3
|
+
## Schema
|
|
4
|
+
|
|
5
|
+
<!-- BEGIN_SQLALCHEMY_DOCS -->
|
|
6
|
+
```mermaid
|
|
7
|
+
|
|
8
|
+
---
|
|
9
|
+
config:
|
|
10
|
+
layout: elk
|
|
11
|
+
---
|
|
12
|
+
erDiagram
|
|
13
|
+
context {
|
|
14
|
+
INTEGER run_id FK "nullable"
|
|
15
|
+
BLOB context "nullable"
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
logs {
|
|
19
|
+
INTEGER run_id FK "nullable"
|
|
20
|
+
VARCHAR log "nullable"
|
|
21
|
+
INTEGER node_id "nullable"
|
|
22
|
+
FLOAT timestamp "nullable"
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
message_ins {
|
|
26
|
+
INTEGER run_id FK "nullable"
|
|
27
|
+
BLOB content "nullable"
|
|
28
|
+
FLOAT created_at "nullable"
|
|
29
|
+
VARCHAR delivered_at "nullable"
|
|
30
|
+
INTEGER dst_node_id "nullable"
|
|
31
|
+
BLOB error "nullable"
|
|
32
|
+
VARCHAR group_id "nullable"
|
|
33
|
+
VARCHAR message_id UK "nullable"
|
|
34
|
+
VARCHAR message_type "nullable"
|
|
35
|
+
VARCHAR reply_to_message_id "nullable"
|
|
36
|
+
INTEGER src_node_id "nullable"
|
|
37
|
+
FLOAT ttl "nullable"
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
message_res {
|
|
41
|
+
INTEGER run_id FK "nullable"
|
|
42
|
+
BLOB content "nullable"
|
|
43
|
+
FLOAT created_at "nullable"
|
|
44
|
+
VARCHAR delivered_at "nullable"
|
|
45
|
+
INTEGER dst_node_id "nullable"
|
|
46
|
+
BLOB error "nullable"
|
|
47
|
+
VARCHAR group_id "nullable"
|
|
48
|
+
VARCHAR message_id UK "nullable"
|
|
49
|
+
VARCHAR message_type "nullable"
|
|
50
|
+
VARCHAR reply_to_message_id "nullable"
|
|
51
|
+
INTEGER src_node_id "nullable"
|
|
52
|
+
FLOAT ttl "nullable"
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
node {
|
|
56
|
+
FLOAT heartbeat_interval "nullable"
|
|
57
|
+
VARCHAR last_activated_at "nullable"
|
|
58
|
+
VARCHAR last_deactivated_at "nullable"
|
|
59
|
+
INTEGER node_id UK "nullable"
|
|
60
|
+
TIMESTAMP online_until "nullable"
|
|
61
|
+
VARCHAR owner_aid "nullable"
|
|
62
|
+
VARCHAR owner_name "nullable"
|
|
63
|
+
BLOB public_key UK "nullable"
|
|
64
|
+
VARCHAR registered_at "nullable"
|
|
65
|
+
VARCHAR status "nullable"
|
|
66
|
+
VARCHAR unregistered_at "nullable"
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
object_children {
|
|
70
|
+
VARCHAR child_id PK,FK
|
|
71
|
+
VARCHAR parent_id PK,FK
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
objects {
|
|
75
|
+
VARCHAR object_id PK "nullable"
|
|
76
|
+
BLOB content "nullable"
|
|
77
|
+
INTEGER is_available
|
|
78
|
+
INTEGER ref_count
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
run {
|
|
82
|
+
INTEGER bytes_recv "nullable"
|
|
83
|
+
INTEGER bytes_sent "nullable"
|
|
84
|
+
FLOAT clientapp_runtime "nullable"
|
|
85
|
+
VARCHAR details "nullable"
|
|
86
|
+
VARCHAR fab_hash "nullable"
|
|
87
|
+
VARCHAR fab_id "nullable"
|
|
88
|
+
VARCHAR fab_version "nullable"
|
|
89
|
+
VARCHAR federation "nullable"
|
|
90
|
+
BLOB federation_options "nullable"
|
|
91
|
+
VARCHAR finished_at "nullable"
|
|
92
|
+
VARCHAR flwr_aid "nullable"
|
|
93
|
+
VARCHAR override_config "nullable"
|
|
94
|
+
VARCHAR pending_at "nullable"
|
|
95
|
+
INTEGER run_id UK "nullable"
|
|
96
|
+
VARCHAR running_at "nullable"
|
|
97
|
+
VARCHAR starting_at "nullable"
|
|
98
|
+
VARCHAR sub_status "nullable"
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
run_objects {
|
|
102
|
+
VARCHAR object_id PK,FK
|
|
103
|
+
INTEGER run_id PK
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
token_store {
|
|
107
|
+
INTEGER run_id PK "nullable"
|
|
108
|
+
FLOAT active_until "nullable"
|
|
109
|
+
VARCHAR token UK
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
run ||--o| context : run_id
|
|
113
|
+
run ||--o{ logs : run_id
|
|
114
|
+
run ||--o{ message_ins : run_id
|
|
115
|
+
run ||--o{ message_res : run_id
|
|
116
|
+
objects ||--o| object_children : parent_id
|
|
117
|
+
objects ||--o| object_children : child_id
|
|
118
|
+
objects ||--o| run_objects : object_id
|
|
119
|
+
|
|
120
|
+
```
|
|
121
|
+
<!-- END_SQLALCHEMY_DOCS -->
|
|
@@ -0,0 +1,15 @@
|
|
|
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
|
+
"""Flower SQLAlchemy database schema."""
|
|
@@ -0,0 +1,36 @@
|
|
|
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
|
+
"""SQLAlchemy Core Table definitions for CoreState."""
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
from sqlalchemy import Column, Float, Integer, MetaData, String, Table
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def create_corestate_metadata() -> MetaData:
|
|
22
|
+
"""Create and return MetaData with CoreState table definitions."""
|
|
23
|
+
metadata = MetaData()
|
|
24
|
+
|
|
25
|
+
# --------------------------------------------------------------------------
|
|
26
|
+
# Table: token_store
|
|
27
|
+
# --------------------------------------------------------------------------
|
|
28
|
+
Table(
|
|
29
|
+
"token_store",
|
|
30
|
+
metadata,
|
|
31
|
+
Column("run_id", Integer, primary_key=True, nullable=True),
|
|
32
|
+
Column("token", String, unique=True, nullable=False),
|
|
33
|
+
Column("active_until", Float),
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
return metadata
|