Source code for optiwindnet.db.migrate
# SPDX-License-Identifier: MIT
# https://gitlab.windenergy.dtu.dk/TOPFARM/OptiWindNet/
"""Migrate a v2 (Pony ORM) or v3 (Peewee) database to v4 (Peewee, slim RouteSet schema).
Uses sqlite3 to read the source file (no Pony dependency), writes v4 with
Peewee. The RouteSet field `num_gates` is renamed to `feeders_per_root`,
and the columns dropped in v4 (`valid`, `is_normalized`, `stuntC`) are
discarded.
Usage:
python -m optiwindnet.db.migrate input.sqlite output.v4.sqlite
"""
import json
import os
import sqlite3
import sys
from . import Machine, Method, NodeSet, RouteSet, open_database
from .model import database_proxy
def _get_source_table_name(src_conn, candidate_names):
"""Find the actual table name in the source database from candidates."""
cursor = src_conn.execute("SELECT name FROM sqlite_master WHERE type='table'")
existing = {row[0] for row in cursor}
for name in candidate_names:
if name in existing:
return name
raise ValueError(
f'None of {candidate_names} found in source database.'
f' Existing tables: {existing}'
)
[docs]
def migrate(src_path, v4_path):
"""Migrate a v2 (Pony ORM) or v3 (Peewee) database to v4 Peewee format.
Args:
src_path: Path to the existing v2 or v3 database file.
v4_path: Path for the new v4 database file (must not exist).
"""
src_path = os.path.abspath(os.path.expanduser(src_path))
v4_path = os.path.abspath(os.path.expanduser(v4_path))
if not os.path.exists(src_path):
raise OSError(f'source database not found: {src_path}')
if os.path.exists(v4_path):
raise OSError(f'v4 database already exists: {v4_path}')
# Open the source with raw sqlite3
src_conn = sqlite3.connect(src_path)
src_conn.row_factory = sqlite3.Row
# Discover table names (v2/Pony uses PascalCase, v3/Peewee uses lowercase)
ns_table = _get_source_table_name(src_conn, ['NodeSet', 'nodeset'])
method_table = _get_source_table_name(src_conn, ['Method', 'method'])
machine_table = _get_source_table_name(src_conn, ['Machine', 'machine'])
rs_table = _get_source_table_name(src_conn, ['RouteSet', 'routeset'])
# Create v4 database
v4_db = open_database(v4_path, create_db=True)
with database_proxy.atomic():
# --- Migrate NodeSet ---
src_rows = src_conn.execute(f'SELECT * FROM "{ns_table}"').fetchall()
for row in src_rows:
row_dict = dict(row)
# IntArray columns are stored as JSON text
for col in ('constraint_groups', 'constraint_vertices'):
val = row_dict[col]
if isinstance(val, str):
row_dict[col] = json.loads(val)
NodeSet.create(**row_dict)
ns_count = len(src_rows)
print(f' NodeSet: {ns_count} rows migrated')
# --- Migrate Method ---
src_rows = src_conn.execute(f'SELECT * FROM "{method_table}"').fetchall()
for row in src_rows:
row_dict = dict(row)
# options is stored as JSON text
val = row_dict.get('options')
if isinstance(val, str):
row_dict['options'] = json.loads(val)
Method.create(**row_dict)
method_count = len(src_rows)
print(f' Method: {method_count} rows migrated')
# --- Migrate Machine ---
src_rows = src_conn.execute(f'SELECT * FROM "{machine_table}"').fetchall()
for row in src_rows:
row_dict = dict(row)
val = row_dict.get('attrs')
if isinstance(val, str):
row_dict['attrs'] = json.loads(val)
Machine.create(**row_dict)
machine_count = len(src_rows)
print(f' Machine: {machine_count} rows migrated')
# --- Migrate RouteSet ---
src_rows = src_conn.execute(f'SELECT * FROM "{rs_table}"').fetchall()
# JSON columns in RouteSet (`num_gates` is the legacy v2/v3 name)
json_cols = {
'num_gates',
'feeders_per_root',
'edges',
'tentative',
'rogue',
'clone2prime',
'misc',
}
for row in src_rows:
row_dict = dict(row)
# Parse JSON text columns
for col in json_cols:
if col in row_dict:
val = row_dict[col]
if isinstance(val, str):
row_dict[col] = json.loads(val)
# `num_gates` was renamed to `feeders_per_root` in v4
if 'feeders_per_root' not in row_dict and 'num_gates' in row_dict:
row_dict['feeders_per_root'] = row_dict.pop('num_gates')
else:
row_dict.pop('num_gates', None)
# Drop columns removed in v4
for col in ('valid', 'is_normalized', 'stuntC'):
row_dict.pop(col, None)
# Ensure misc is never NULL
if row_dict.get('misc') is None:
row_dict['misc'] = {}
RouteSet.create(**row_dict)
rs_count = len(src_rows)
print(f' RouteSet: {rs_count} rows migrated')
src_conn.close()
# Verify counts
assert NodeSet.select().count() == ns_count
assert Method.select().count() == method_count
assert Machine.select().count() == machine_count
assert RouteSet.select().count() == rs_count
print(f'\nMigration complete: {v4_path}')
print(
f' NodeSet: {ns_count}, Method: {method_count}, '
f'Machine: {machine_count}, RouteSet: {rs_count}'
)
v4_db.close()
[docs]
def main():
if len(sys.argv) != 3:
print('Usage: python -m optiwindnet.db.migrate <v2-or-v3.sqlite> <v4.sqlite>')
sys.exit(1)
migrate(sys.argv[1], sys.argv[2])
if __name__ == '__main__':
main()