Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
d40c3b5
Init
chongshenng Jan 27, 2026
45d2d81
Merge branch 'main' into refactor/sqlite-in-mem-db-url
chongshenng Jan 27, 2026
a899d1f
feat(framework): Add `paracelsus` to autogenerate SQLAlchemy schema (…
chongshenng Jan 27, 2026
c8ea325
Update text and language files (#6455)
github-actions[bot] Jan 28, 2026
6cd1479
docs(framework): Update pages to use Flower Configuration instead of …
jafermarq Jan 28, 2026
e4990b2
Merge branch 'main' into refactor/sqlite-in-mem-db-url
chongshenng Jan 28, 2026
c5f4eeb
feat(framework): Introduce Alembic for SuperLink state migration
chongshenng Jan 28, 2026
6494e01
Initial commit of Alembic migration utils
chongshenng Jan 28, 2026
9c8e01a
Fix importable module name error in wheel
chongshenng Jan 28, 2026
ca8b22f
Docstring
chongshenng Jan 28, 2026
8b60399
Merge branch 'feat/introduce-alembic' into feat/add-alembic-migration…
chongshenng Jan 28, 2026
6734a74
Make SqlLinkState start with Alembic
chongshenng Jan 28, 2026
2391759
Update
chongshenng Jan 28, 2026
20ab460
Apply isort
chongshenng Jan 28, 2026
f6d492a
Use info level log
chongshenng Jan 28, 2026
4241447
Merge branch 'feat/add-alembic-migration-utils' into feat/start-sqlin…
chongshenng Jan 28, 2026
893591c
Fix utils.py
chongshenng Jan 28, 2026
13f8990
Remove unnecessary logging
chongshenng Jan 28, 2026
e80f476
Update migration utils
chongshenng Jan 28, 2026
a558e36
Refactor
chongshenng Jan 28, 2026
cc0a05c
Change nams
chongshenng Jan 28, 2026
180f7e8
Refactor
chongshenng Jan 28, 2026
f21901d
Merge main
chongshenng Jan 28, 2026
aacc656
Resolve conflicts
chongshenng Jan 28, 2026
f872097
Merge branch 'main' into feat/add-alembic-migration-utils
chongshenng Jan 28, 2026
66173a4
Merge main
chongshenng Jan 28, 2026
f744ada
Refactor
chongshenng Jan 28, 2026
f551210
Better docstring
chongshenng Jan 28, 2026
09fe4ed
Use in-memory DB at baseline revision to reflect its schema
chongshenng Jan 28, 2026
2ad94fa
Improve docstrings and comments
chongshenng Jan 28, 2026
c696d92
Merge branch 'feat/add-alembic-migration-utils' into feat/start-sqlin…
chongshenng Jan 28, 2026
1126023
Lint
chongshenng Jan 28, 2026
e40af18
Add comments
chongshenng Jan 28, 2026
6278eb7
Merge branch 'feat/add-alembic-migration-utils' into feat/start-sqlin…
chongshenng Jan 28, 2026
a87da26
Improve tests
chongshenng Jan 28, 2026
ada2da5
Remove redundant test
chongshenng Jan 28, 2026
ad89424
Merge branch 'feat/add-alembic-migration-utils' into feat/start-sqlin…
chongshenng Jan 28, 2026
cf9c7af
Revert
chongshenng Jan 28, 2026
a107849
Add upgraded successful info log
chongshenng Jan 29, 2026
60291c4
Merge branch 'feat/add-alembic-migration-utils' into feat/start-sqlin…
chongshenng Jan 29, 2026
f26fffe
Resolve conflicts
chongshenng Jan 29, 2026
997a52f
Update framework/py/flwr/supercore/sql_mixin.py
chongshenng Jan 29, 2026
f6fbb68
Update framework/py/flwr/supercore/sql_mixin.py
chongshenng Jan 29, 2026
1e24e2d
Fix return type
chongshenng Jan 29, 2026
0045208
Merge branch 'main' into feat/start-sqlinkstate-with-alembic
chongshenng Jan 29, 2026
13539c2
Revert commented lines
chongshenng Jan 29, 2026
4b459db
Merge branch 'main' into feat/start-sqlinkstate-with-alembic
chongshenng Jan 29, 2026
a295d3b
Remove redundant initialization test
chongshenng Jan 29, 2026
c9d1f89
Merge branch 'main' into feat/start-sqlinkstate-with-alembic
chongshenng Jan 29, 2026
1647f7c
Merge branch 'main' into feat/start-sqlinkstate-with-alembic
chongshenng Jan 29, 2026
1550aa4
Cache SQL instances to prevent race conditions in concurrent Alembic …
chongshenng Jan 29, 2026
44564ce
Merge branch 'main' into feat/start-sqlinkstate-with-alembic
chongshenng Jan 29, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -56,20 +56,28 @@ def __init__(

def state(self) -> LinkState:
"""Return a State instance and create it, if necessary."""
# Return cached state if it exists
if self.state_instance is not None:
if self.database == FLWR_IN_MEMORY_DB_NAME:
log(DEBUG, "Using InMemoryState")
else:
log(DEBUG, "Using SqlLinkState")
return self.state_instance

# Get the ObjectStore instance
object_store = self.objectstore_factory.store()

# InMemoryState
if self.database == FLWR_IN_MEMORY_DB_NAME:
if self.state_instance is None:
self.state_instance = InMemoryLinkState(
self.federation_manager, object_store
)
self.state_instance = InMemoryLinkState(
self.federation_manager, object_store
)
log(DEBUG, "Using InMemoryState")
return self.state_instance

# SqlLinkState
state = SqlLinkState(self.database, self.federation_manager, object_store)
state.initialize()
self.state_instance = state
log(DEBUG, "Using SqlLinkState")
return state
22 changes: 0 additions & 22 deletions framework/py/flwr/server/superlink/linkstate/linkstate_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -1823,17 +1823,6 @@ def state_factory(self) -> SqlLinkState:
state.initialize()
return state

def test_initialize(self) -> None:
"""Test initialization."""
# Prepare
state = self.state_factory()

# Execute
result = state.query("SELECT name FROM sqlite_schema;")

# Assert - 7 tables + 11 indexes (3 explicit + 8 auto from UNIQUE constraints)
assert len(result) == 18


class SqlFileBasedTest(StateTest, unittest.TestCase):
"""Test SqlLinkState implementation with file-based database."""
Expand All @@ -1852,17 +1841,6 @@ def state_factory(self) -> SqlLinkState:
state.initialize()
return state

def test_initialize(self) -> None:
"""Test initialization."""
# Prepare
state = self.state_factory()

# Execute
result = state.query("SELECT name FROM sqlite_schema;")

# Assert - 7 tables + 11 indexes (3 explicit + 8 auto from UNIQUE constraints)
assert len(result) == 18


if __name__ == "__main__":
unittest.main(verbosity=2)
Original file line number Diff line number Diff line change
Expand Up @@ -49,15 +49,23 @@ def store(self) -> ObjectStore:
ObjectStore
An ObjectStore instance for storing objects by object_id.
"""
# Return cached store if it exists
if self.store_instance is not None:
if self.database == FLWR_IN_MEMORY_DB_NAME:
log(DEBUG, "Using InMemoryObjectStore")
else:
log(DEBUG, "Using SqlObjectStore")
return self.store_instance

# InMemoryObjectStore
if self.database == FLWR_IN_MEMORY_DB_NAME:
if self.store_instance is None:
self.store_instance = InMemoryObjectStore()
self.store_instance = InMemoryObjectStore()
log(DEBUG, "Using InMemoryObjectStore")
return self.store_instance

# SqlObjectStore
store = SqlObjectStore(self.database)
store.initialize()
self.store_instance = store
log(DEBUG, "Using SqlObjectStore")
return store
22 changes: 20 additions & 2 deletions framework/py/flwr/supercore/object_store/object_store_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,8 +18,10 @@
import tempfile
import unittest
from abc import abstractmethod
from typing import cast

from parameterized import parameterized
from sqlalchemy import Engine, inspect

from flwr.common.inflatable import get_object_id, get_object_tree, iterate_object_tree
from flwr.common.inflatable_test import CustomDataClass
Expand Down Expand Up @@ -377,12 +379,20 @@ class SqlInMemoryObjectStoreTest(ObjectStoreTest):

__test__ = True

def object_store_factory(self) -> ObjectStore:
def object_store_factory(self) -> SqlObjectStore:
"""Return SqlObjectStore."""
store = SqlObjectStore(":memory:")
store.initialize()
return store

def test_in_memory_does_not_create_alembic_version(self) -> None:
"""Ensure in-memory DB uses create_all without Alembic versioning."""
store = self.object_store_factory()
table_names = inspect(
cast(Engine, store._engine) # pylint: disable=W0212
).get_table_names()
self.assertNotIn("alembic_version", table_names)


class SqlFileBasedObjectStoreTest(ObjectStoreTest):
"""Test SqlObjectStore implementation with file-based database."""
Expand All @@ -399,8 +409,16 @@ def tearDown(self) -> None:
super().tearDown()
self.temp_file.close()

def object_store_factory(self) -> ObjectStore:
def object_store_factory(self) -> SqlObjectStore:
"""Return SqlObjectStore."""
store = SqlObjectStore(self.temp_file.name)
store.initialize()
return store

def test_file_db_creates_alembic_version(self) -> None:
"""Ensure file-based DBs run Alembic migrations."""
store = self.object_store_factory()
table_names = inspect(
cast(Engine, store._engine) # pylint: disable=W0212
).get_table_names()
self.assertIn("alembic_version", table_names)
10 changes: 8 additions & 2 deletions framework/py/flwr/supercore/sql_mixin.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@

from flwr.common.logger import log
from flwr.supercore.constant import FLWR_IN_MEMORY_SQLITE_DB_URL, SQLITE_PRAGMAS
from flwr.supercore.state.alembic.utils import run_migrations

_current_session: ContextVar[Session | None] = ContextVar(
"current_sqlalchemy_session",
Expand Down Expand Up @@ -193,9 +194,14 @@ def initialize(self, log_queries: bool = False) -> list[str]:
# Create session factory
self._session_factory = sessionmaker(bind=self._engine)

# Create tables defined in metadata (idempotent - only creates missing tables)
if (metadata := self.get_metadata()) is not None:
# Create database
metadata: MetaData | None = self.get_metadata()
if metadata and self.database_url == FLWR_IN_MEMORY_SQLITE_DB_URL:
# In-memory databases: create tables directly from SQLAlchemy metadata
metadata.create_all(self._engine)
else:
# File-based databases: use Alembic migrations for schema versioning
run_migrations(self._engine)

# Get all table names using inspector
inspector = inspect(self._engine)
Expand Down
Loading