Files
ComfyAssets-ComfyUI_PromptM…/tools/workflow_text_digests.py
T
Vito Sansevero 8d61eda691 feat(maintenance): one kept pre-migration backup and nothing-to-do refusal
Re-review fixes R2, R3, R4, R5 and R6 (job side):

- R3: Apply refuses a plan with nothing to rewrite (409 nothing-to-do, no
  job, no backup). Only the first apply keeps a pre-workflow-storage
  backup; later applies (after a Cancel, a rerun) take a pooled safety
  backup, forced on whatever the policy, so one copy is never pruned.
- R4: a second-connection writer test runs against the live job loop; with
  its pause removed the writer hits "database is locked" (4 of 5 links).
- R5: the digest tool loads only database/workflow_documents.py by path (no
  package start-up, no log handler) and opens a stopped database with
  immutable=1, so it leaves no -wal/-shm; a live one is read through its WAL.
- R6: a backup that fails on a damaged document says which one in the job
  line.
- R2: README's changelog says images recorded by this version use the new
  form from the first start, and the way back is the earliest
  pre-workflow-storage backup (or a pre-update backup if the job never ran).
2026-10-24 06:13:06 -07:00

95 lines
3.5 KiB
Python

"""Digests of every image record's stored workflow and prompt graph text.
Proves the workflow storage rewrite (#249 S6) kept every text byte for byte.
Run it from this repository on the database before the rewrite (or on its
``prompts.db.pre-workflow-storage_*`` backup) and again after, then compare::
python3 tools/workflow_text_digests.py prompts.db.pre-workflow-storage_<stamp> \\
before.sha
python3 tools/workflow_text_digests.py prompts.db after.sha
diff before.sha after.sha # prints nothing when every text is identical
Each line is ``<image id> <sha256 of workflow_data> <sha256 of prompt_metadata>``
(``-`` for none), hashing the exact stored bytes: the inline column before the
rewrite, the document read through the one accessor after it.
Read-only, with no side effects: the database is opened with ``mode=ro``,
and with ``immutable=1`` when no ``-wal``/``-shm`` file exists beside it (a
stopped database or a backup), so SQLite creates none. A live database
already has them and is read through its WAL. Only
``database/workflow_documents.py`` is loaded (by path; it needs only the
standard library), so no package start-up runs and no log file or handler is
added. Run it as the user that runs ComfyUI.
"""
import argparse
import hashlib
import importlib.util
import os
import sqlite3
import sys
from pathlib import Path
_ROOT = Path(__file__).resolve().parents[1]
def _load_documents():
"""``database/workflow_documents.py`` alone, without the ``database`` package."""
path = _ROOT / "database" / "workflow_documents.py"
spec = importlib.util.spec_from_file_location("_pm_workflow_documents", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module
def database_uri(db_path):
"""``mode=ro``, plus ``immutable=1`` when nothing has the database open."""
path = os.path.abspath(db_path)
uri = Path(path).as_uri() + "?mode=ro"
if not any(os.path.exists(path + suffix) for suffix in ("-wal", "-shm")):
uri += "&immutable=1"
return uri
def _digest(value):
if value is None:
return "-"
raw = value if isinstance(value, bytes) else value.encode("utf-8")
return hashlib.sha256(raw).hexdigest()
def write_digests(db_path, out):
"""Write one digest line per image record of ``db_path`` to ``out``."""
documents = _load_documents()
conn = sqlite3.connect(database_uri(db_path), uri=True)
try:
conn.row_factory = sqlite3.Row
conn.text_factory = bytes # inline text exactly as stored, even undecodable
reader = documents.DocumentReader(conn)
count = 0
with open(out, "w", encoding="utf-8") as handle:
for row in conn.execute("SELECT * FROM generated_images ORDER BY id"):
texts = documents.resolve_image_row(reader, row)
handle.write(
f"{row['id']} {_digest(texts.get('workflow_data'))} "
f"{_digest(texts.get('prompt_metadata'))}\n"
)
count += 1
return count
finally:
conn.close()
def main(argv=None):
parser = argparse.ArgumentParser(description=__doc__.splitlines()[0])
parser.add_argument("database", help="prompts.db or one of its backups")
parser.add_argument("out", help="file to write the digests to")
args = parser.parse_args(argv)
count = write_digests(args.database, args.out)
print(f"{count} image records")
return 0
if __name__ == "__main__":
sys.exit(main())