chore: import upstream snapshot with attribution
docs / deploy (push) Has been cancelled
docs / changes (push) Has been cancelled
docs / check-and-build (push) Has been cancelled
build container image / cpu (push) Has been cancelled
build container image / cuda (push) Has been cancelled
build container image / rocm (push) Has been cancelled
frontend checks / frontend-checks (push) Has been cancelled
frontend tests / frontend-tests (push) Has been cancelled
lfs checks / lfs-check (push) Has been cancelled
python checks / python-checks (push) Has been cancelled
python tests / py3.12: macos-default (push) Has been cancelled
python tests / py3.11: windows-cpu (push) Has been cancelled
python tests / py3.12: windows-cpu (push) Has been cancelled
python tests / py3.11: linux-cpu (push) Has been cancelled
typegen checks / typegen-checks (push) Has been cancelled
uv lock checks / uv-lock-checks (push) Has been cancelled
openapi checks / openapi-checks (push) Has been cancelled
python tests / py3.11: macos-default (push) Has been cancelled
python tests / py3.12: linux-cpu (push) Has been cancelled

This commit is contained in:
wehub-resource-sync
2026-07-13 13:22:06 +08:00
commit cddb07a176
3370 changed files with 685519 additions and 0 deletions
@@ -0,0 +1,70 @@
import sqlite3
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_2026_07_01_add_workflow_call_queue_metadata import (
AddWorkflowCallQueueMetadataCallback,
build_migration,
)
def _get_columns(cursor: sqlite3.Cursor, table_name: str) -> set[str]:
cursor.execute(f"PRAGMA table_info({table_name});")
return {row[1] for row in cursor.fetchall()}
def _get_indexes(cursor: sqlite3.Cursor) -> set[str]:
cursor.execute("SELECT name FROM sqlite_master WHERE type = 'index';")
return {row[0] for row in cursor.fetchall()}
def test_adds_workflow_call_columns_and_indexes_to_session_queue() -> None:
db = sqlite3.connect(":memory:")
cursor = db.cursor()
cursor.execute("CREATE TABLE session_queue (item_id INTEGER PRIMARY KEY);")
AddWorkflowCallQueueMetadataCallback()(cursor)
assert _get_columns(cursor, "session_queue") >= {
"workflow_call_id",
"parent_item_id",
"parent_session_id",
"root_item_id",
"workflow_call_depth",
}
assert _get_indexes(cursor) >= {
"idx_session_queue_workflow_call_id",
"idx_session_queue_parent_item_id",
"idx_session_queue_parent_session_id",
"idx_session_queue_root_item_id",
"idx_session_queue_workflow_call_depth",
}
db.close()
def test_migration_is_idempotent_and_tolerates_missing_session_queue() -> None:
db = sqlite3.connect(":memory:")
cursor = db.cursor()
AddWorkflowCallQueueMetadataCallback()(cursor)
cursor.execute("CREATE TABLE session_queue (item_id INTEGER PRIMARY KEY, workflow_call_id TEXT);")
AddWorkflowCallQueueMetadataCallback()(cursor)
AddWorkflowCallQueueMetadataCallback()(cursor)
assert _get_columns(cursor, "session_queue") >= {
"workflow_call_id",
"parent_item_id",
"parent_session_id",
"root_item_id",
"workflow_call_depth",
}
db.close()
def test_build_migration_declares_stable_id_and_dependency() -> None:
migration = build_migration()
assert migration.id == "2026_07_01_add_workflow_call_queue_metadata"
assert migration.depends_on == "migration_33"
assert migration.from_version is None
assert migration.to_version is None
@@ -0,0 +1,91 @@
"""Tests for migration 31: Add image_subfolder column to images table."""
import sqlite3
import pytest
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_31 import (
Migration31Callback,
build_migration_31,
)
@pytest.fixture
def db() -> sqlite3.Connection:
"""In-memory SQLite database with a minimal images table mimicking pre-migration schema."""
conn = sqlite3.connect(":memory:")
conn.execute(
"""
CREATE TABLE images (
image_name TEXT NOT NULL PRIMARY KEY,
image_origin TEXT NOT NULL,
image_category TEXT NOT NULL,
width INTEGER NOT NULL DEFAULT 0,
height INTEGER NOT NULL DEFAULT 0,
session_id TEXT,
node_id TEXT,
metadata TEXT,
is_intermediate BOOLEAN DEFAULT FALSE,
created_at DATETIME NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%f', 'NOW')),
updated_at DATETIME NOT NULL DEFAULT (STRFTIME('%Y-%m-%dT%H:%M:%f', 'NOW')),
deleted_at DATETIME,
starred BOOLEAN NOT NULL DEFAULT FALSE,
has_workflow BOOLEAN NOT NULL DEFAULT FALSE
);
"""
)
return conn
class TestMigration31:
def test_adds_image_subfolder_column(self, db: sqlite3.Connection):
"""Migration adds image_subfolder column to existing images table."""
callback = Migration31Callback()
cursor = db.cursor()
callback(cursor)
cursor.execute("PRAGMA table_info(images);")
columns = {row[1] for row in cursor.fetchall()}
assert "image_subfolder" in columns
def test_existing_rows_get_empty_string_default(self, db: sqlite3.Connection):
"""Pre-existing image rows should get image_subfolder = '' after migration."""
db.execute(
"INSERT INTO images (image_name, image_origin, image_category, width, height, has_workflow) "
"VALUES ('old_image.png', 'internal', 'general', 512, 512, 0)"
)
db.commit()
callback = Migration31Callback()
callback(db.cursor())
db.commit()
row = db.execute("SELECT image_subfolder FROM images WHERE image_name = 'old_image.png'").fetchone()
assert row is not None
assert row[0] == ""
def test_idempotent_migration(self, db: sqlite3.Connection):
"""Running migration twice should not fail (column already exists)."""
callback = Migration31Callback()
cursor = db.cursor()
callback(cursor)
# Running again should be safe
callback(cursor)
cursor.execute("PRAGMA table_info(images);")
columns = [row[1] for row in cursor.fetchall()]
assert columns.count("image_subfolder") == 1
def test_no_images_table_is_noop(self):
"""If images table doesn't exist, migration is a no-op."""
conn = sqlite3.connect(":memory:")
callback = Migration31Callback()
cursor = conn.cursor()
# Should not raise
callback(cursor)
def test_build_migration_31_version_numbers(self):
"""build_migration_31 returns correct version range."""
migration = build_migration_31()
assert migration.from_version == 30
assert migration.to_version == 31
@@ -0,0 +1,131 @@
"""Tests for migration 32: Repair model_relationships foreign keys."""
import sqlite3
import pytest
from invokeai.app.services.shared.sqlite_migrator.migrations.migration_32 import (
Migration32Callback,
build_migration_32,
)
def _create_models_table(conn: sqlite3.Connection) -> None:
conn.execute(
"""
CREATE TABLE models (
id TEXT NOT NULL PRIMARY KEY,
config TEXT NOT NULL
);
"""
)
def _create_broken_relationships_table(conn: sqlite3.Connection) -> None:
"""Recreates the broken state left by migration 22: FKs reference the dropped models_old table."""
conn.execute(
"""
CREATE TABLE model_relationships (
model_key_1 TEXT NOT NULL,
model_key_2 TEXT NOT NULL,
created_at TEXT DATETIME NOT NULL DEFAULT(STRFTIME('%Y-%m-%d %H:%M:%f', 'NOW')),
PRIMARY KEY (model_key_1, model_key_2),
FOREIGN KEY (model_key_1) REFERENCES "models_old"(id) ON DELETE CASCADE,
FOREIGN KEY (model_key_2) REFERENCES "models_old"(id) ON DELETE CASCADE
);
"""
)
conn.execute("CREATE INDEX keyx_model_relationships_model_key_2 ON model_relationships(model_key_2);")
@pytest.fixture
def db() -> sqlite3.Connection:
conn = sqlite3.connect(":memory:")
_create_models_table(conn)
_create_broken_relationships_table(conn)
conn.execute("INSERT INTO models (id, config) VALUES ('a', '{}'), ('b', '{}'), ('c', '{}')")
return conn
class TestMigration32:
def test_repoints_foreign_keys_to_models(self, db: sqlite3.Connection):
"""After migration, the foreign keys reference models, not models_old."""
Migration32Callback()(db.cursor())
db.commit()
sql = db.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='model_relationships'").fetchone()[
0
]
assert "models_old" not in sql
assert "REFERENCES models(id)" in sql
def test_preserves_valid_links(self, db: sqlite3.Connection):
"""Links between existing models are preserved."""
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'b')")
db.commit()
Migration32Callback()(db.cursor())
db.commit()
rows = db.execute("SELECT model_key_1, model_key_2 FROM model_relationships ORDER BY model_key_1").fetchall()
assert rows == [("a", "b")]
def test_drops_orphaned_links(self, db: sqlite3.Connection):
"""Links referencing missing models are dropped so the restored FKs are satisfiable."""
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'b')")
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'gone')")
db.commit()
Migration32Callback()(db.cursor())
db.commit()
rows = db.execute("SELECT model_key_1, model_key_2 FROM model_relationships").fetchall()
assert rows == [("a", "b")]
def test_cascade_works_after_repair(self, db: sqlite3.Connection):
"""ON DELETE CASCADE against models works once the FKs are repaired."""
db.execute("INSERT INTO model_relationships (model_key_1, model_key_2) VALUES ('a', 'b')")
db.commit()
Migration32Callback()(db.cursor())
db.commit()
db.execute("PRAGMA foreign_keys = ON;")
db.execute("DELETE FROM models WHERE id = 'a'")
db.commit()
rows = db.execute("SELECT * FROM model_relationships").fetchall()
assert rows == []
def test_index_recreated(self, db: sqlite3.Connection):
"""The lookup index on model_key_2 is recreated on the rebuilt table."""
Migration32Callback()(db.cursor())
db.commit()
idx = db.execute(
"SELECT name FROM sqlite_master WHERE type='index' AND name='keyx_model_relationships_model_key_2'"
).fetchone()
assert idx is not None
def test_idempotent_when_already_correct(self, db: sqlite3.Connection):
"""Running on an already-correct table is a no-op (no rebuild)."""
Migration32Callback()(db.cursor())
db.commit()
# Second run should detect the correct FKs and do nothing.
Migration32Callback()(db.cursor())
db.commit()
sql = db.execute("SELECT sql FROM sqlite_master WHERE type='table' AND name='model_relationships'").fetchone()[
0
]
assert "REFERENCES models(id)" in sql
def test_no_relationships_table_is_noop(self):
"""If the table doesn't exist, migration is a no-op."""
conn = sqlite3.connect(":memory:")
Migration32Callback()(conn.cursor()) # should not raise
def test_build_migration_32_version_numbers(self):
migration = build_migration_32()
assert migration.from_version == 31
assert migration.to_version == 32
@@ -0,0 +1,252 @@
import importlib
from logging import Logger
from pathlib import Path
import pytest
from invokeai.app.services.shared.sqlite_migrator.migration_loader import (
MigrationBuildContext,
MigrationLoaderError,
build_migrations,
)
def _write_package(tmp_path: Path, package_name: str, modules: dict[str, str]) -> str:
package_path = tmp_path / package_name
package_path.mkdir()
(package_path / "__init__.py").write_text("", encoding="utf-8")
for module_name, module_source in modules.items():
(package_path / f"{module_name}.py").write_text(module_source, encoding="utf-8")
importlib.invalidate_caches()
return package_name
def test_build_migrations_discovers_modules_in_numeric_order(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_order",
{
"migration_2": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration_2():
return Migration(from_version=1, to_version=2, callback=lambda cursor: None)
""",
"migration_1": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration_1():
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
""",
"not_a_migration": "VALUE = 1",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
migrations = build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
assert [migration.id for migration in migrations] == ["migration_1", "migration_2"]
def test_build_migrations_injects_requested_dependencies(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_injection",
{
"migration_1": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration_1(app_config, logger, image_files):
assert app_config == "config"
assert logger.name == "test"
assert image_files == "images"
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
""",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
migrations = build_migrations(
MigrationBuildContext(app_config="config", logger=Logger("test"), image_files="images"),
package_name=package_name,
)
assert [migration.id for migration in migrations] == ["migration_1"]
def test_build_migrations_supports_dated_descriptive_modules(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_dated",
{
"migration_2026_06_30_add_example_table": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration():
return Migration(
id="2026_06_30_add_example_table",
depends_on="migration_1",
callback=lambda cursor: None,
)
""",
"migration_1": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration_1():
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
""",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
migrations = build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
assert [migration.id for migration in migrations] == ["migration_1", "2026_06_30_add_example_table"]
def test_build_migrations_rejects_dated_module_id_mismatch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_dated_id_mismatch",
{
"migration_2026_06_30_add_example_table": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration():
return Migration(
id="2026_06_30_typo",
depends_on="migration_1",
callback=lambda cursor: None,
)
""",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
with pytest.raises(MigrationLoaderError, match="must return migration id"):
build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
def test_build_migrations_rejects_numeric_module_id_mismatch(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_numeric_id_mismatch",
{
"migration_1": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration_1():
return Migration(id="wrong", from_version=0, to_version=1, callback=lambda cursor: None)
""",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
with pytest.raises(MigrationLoaderError, match="must return migration id"):
build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
def test_build_migrations_rejects_unknown_builder_dependency(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_unknown_dependency",
{
"migration_1": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_migration_1(unknown_service):
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
""",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
with pytest.raises(MigrationLoaderError, match="unknown dependency"):
build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
def test_build_migrations_rejects_missing_expected_builder(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_missing_builder",
{
"migration_1": """
from invokeai.app.services.shared.sqlite_migrator.sqlite_migrator_common import Migration
def build_something_else():
return Migration(from_version=0, to_version=1, callback=lambda cursor: None)
""",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
with pytest.raises(MigrationLoaderError, match="build_migration_1"):
build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
def test_build_migrations_rejects_non_migration_return(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_bad_return",
{
"migration_1": """
def build_migration_1():
return object()
""",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
with pytest.raises(MigrationLoaderError, match="must return Migration"):
build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
def test_build_migrations_rejects_malformed_migration_module(tmp_path: Path, monkeypatch: pytest.MonkeyPatch) -> None:
package_name = _write_package(
tmp_path,
"test_migrations_malformed",
{
"migration_latest": "VALUE = 1",
},
)
monkeypatch.syspath_prepend(str(tmp_path))
with pytest.raises(MigrationLoaderError, match="Malformed migration module"):
build_migrations(
MigrationBuildContext(app_config=object(), logger=Logger("test"), image_files=object()),
package_name=package_name,
)
def test_build_migrations_discovers_production_migrations(tmp_path: Path) -> None:
class FakeConfig:
root_path = tmp_path
models_path = tmp_path / "models"
convert_cache_path = tmp_path / "models" / ".cache"
legacy_conf_path = tmp_path / "models.yaml"
legacy_conf_dir = tmp_path
migrations = build_migrations(
MigrationBuildContext(app_config=FakeConfig(), logger=Logger("test"), image_files=object())
)
legacy_migrations = [migration for migration in migrations if migration.to_version is not None]
latest_legacy_version = max(migration.to_version or 0 for migration in legacy_migrations)
assert [migration.id for migration in legacy_migrations] == [
f"migration_{i}" for i in range(1, latest_legacy_version + 1)
]
assert [migration.depends_on for migration in legacy_migrations] == [None] + [
f"migration_{i}" for i in range(1, latest_legacy_version)
]