Fix 6 bugs found during code review
- Fix NameError: pass state to _render_vace_settings (tab_batch_ng.py) - Fix non-atomic sync_to_db: use BEGIN IMMEDIATE transaction with rollback - Fix create_secondary() missing db/current_project/db_enabled fields - Fix URL encoding: percent-encode project/file names in API URLs - Fix import_json_file crash on re-import: upsert instead of insert - Fix dual DB instances: share single ProjectDB between UI and API routes - Also fixes top_level metadata never being updated on existing data_files Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
66
utils.py
66
utils.py
@@ -164,7 +164,7 @@ def sync_to_db(db, project_name: str, file_path: Path, data: dict) -> None:
|
||||
"""Dual-write helper: sync JSON data to the project database.
|
||||
|
||||
Resolves (or creates) the data_file, upserts all sequences from batch_data,
|
||||
and saves the history_tree.
|
||||
and saves the history_tree. All writes happen in a single transaction.
|
||||
"""
|
||||
if not db or not project_name:
|
||||
return
|
||||
@@ -173,26 +173,58 @@ def sync_to_db(db, project_name: str, file_path: Path, data: dict) -> None:
|
||||
if not proj:
|
||||
return
|
||||
file_name = Path(file_path).stem
|
||||
df = db.get_data_file(proj["id"], file_name)
|
||||
if not df:
|
||||
|
||||
# Use a single transaction for atomicity
|
||||
db.conn.execute("BEGIN IMMEDIATE")
|
||||
try:
|
||||
df = db.get_data_file(proj["id"], file_name)
|
||||
top_level = {k: v for k, v in data.items()
|
||||
if k not in (KEY_BATCH_DATA, KEY_HISTORY_TREE)}
|
||||
df_id = db.create_data_file(proj["id"], file_name, "generic", top_level)
|
||||
else:
|
||||
df_id = df["id"]
|
||||
if not df:
|
||||
now = __import__('time').time()
|
||||
cur = db.conn.execute(
|
||||
"INSERT INTO data_files (project_id, name, data_type, top_level, created_at, updated_at) "
|
||||
"VALUES (?, ?, ?, ?, ?, ?)",
|
||||
(proj["id"], file_name, "generic", json.dumps(top_level), now, now),
|
||||
)
|
||||
df_id = cur.lastrowid
|
||||
else:
|
||||
df_id = df["id"]
|
||||
# Update top_level metadata
|
||||
now = __import__('time').time()
|
||||
db.conn.execute(
|
||||
"UPDATE data_files SET top_level = ?, updated_at = ? WHERE id = ?",
|
||||
(json.dumps(top_level), now, df_id),
|
||||
)
|
||||
|
||||
# Sync sequences
|
||||
batch_data = data.get(KEY_BATCH_DATA, [])
|
||||
if isinstance(batch_data, list):
|
||||
db.delete_sequences_for_file(df_id)
|
||||
for item in batch_data:
|
||||
seq_num = int(item.get(KEY_SEQUENCE_NUMBER, 0))
|
||||
db.upsert_sequence(df_id, seq_num, item)
|
||||
# Sync sequences
|
||||
batch_data = data.get(KEY_BATCH_DATA, [])
|
||||
if isinstance(batch_data, list):
|
||||
db.conn.execute("DELETE FROM sequences WHERE data_file_id = ?", (df_id,))
|
||||
for item in batch_data:
|
||||
seq_num = int(item.get(KEY_SEQUENCE_NUMBER, 0))
|
||||
now = __import__('time').time()
|
||||
db.conn.execute(
|
||||
"INSERT INTO sequences (data_file_id, sequence_number, data, updated_at) "
|
||||
"VALUES (?, ?, ?, ?)",
|
||||
(df_id, seq_num, json.dumps(item), now),
|
||||
)
|
||||
|
||||
# Sync history tree
|
||||
history_tree = data.get(KEY_HISTORY_TREE)
|
||||
if history_tree and isinstance(history_tree, dict):
|
||||
db.save_history_tree(df_id, history_tree)
|
||||
# Sync history tree
|
||||
history_tree = data.get(KEY_HISTORY_TREE)
|
||||
if history_tree and isinstance(history_tree, dict):
|
||||
now = __import__('time').time()
|
||||
db.conn.execute(
|
||||
"INSERT INTO history_trees (data_file_id, tree_data, updated_at) "
|
||||
"VALUES (?, ?, ?) "
|
||||
"ON CONFLICT(data_file_id) DO UPDATE SET tree_data=excluded.tree_data, updated_at=excluded.updated_at",
|
||||
(df_id, json.dumps(history_tree), now),
|
||||
)
|
||||
|
||||
db.conn.commit()
|
||||
except Exception:
|
||||
db.conn.rollback()
|
||||
raise
|
||||
except Exception as e:
|
||||
logger.warning(f"sync_to_db failed: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user