majuna/app/cli/db.py

125 lines
3.7 KiB
Python
Raw Normal View History

import base64
2022-04-22 12:52:41 +01:00
import datetime
import json
2022-04-22 12:52:41 +01:00
import logging
import sys
from collections import defaultdict
from typing import Any, Callable, Dict, List, Type
from sqlalchemy import inspect
2022-04-22 12:52:41 +01:00
from app import app
from app.cli import BaseCliHandler, _SubparserType
2022-04-22 12:52:41 +01:00
from app.extensions import db
from app.models.activity import Activity, Webhook
from app.models.alarms import Alarm, AlarmState
from app.models.automation import Automation, AutomationLogs, AutomationState
from app.models.base import Group, MirrorList, Pool, PoolGroup
2022-05-06 12:28:11 +01:00
from app.models.bridges import Bridge, BridgeConf
from app.models.mirrors import Origin, Proxy, SmartProxy
from app.models.onions import Eotk, Onion
from app.models.tfstate import TerraformState
2022-04-22 12:52:41 +01:00
Model = Type[db.Model] # type: ignore[name-defined]
# order matters due to foreign key constraints
models: List[Model] = [
Group,
Activity,
Pool,
PoolGroup,
SmartProxy,
Origin,
Proxy,
Onion,
Alarm,
Automation,
AutomationLogs,
BridgeConf,
Bridge,
Eotk,
MirrorList,
TerraformState,
Webhook
]
class ExportEncoder(json.JSONEncoder):
"""Encoder to serialise all types used in the database."""
2024-11-16 19:47:41 +00:00
def default(self, o: Any) -> Any:
if isinstance(o, AlarmState):
return o.name
if isinstance(o, AutomationState):
return o.name
if isinstance(o, bytes):
return base64.encodebytes(o).decode('utf-8')
if isinstance(o, (datetime.datetime, datetime.date, datetime.time)):
return o.isoformat()
return super().default(o)
def model_to_dict(model: Model) -> Dict[str, Any]:
output = {}
inspection = inspect(type(model))
if not inspection:
raise RuntimeError(f"Could not inspect model {model}")
for column in inspection.columns:
item = getattr(model, column.name)
output[f"{type(item).__name__}_{column.name}"] = item
return output
def db_export() -> None:
encoder = ExportEncoder()
output = defaultdict(list)
for model in models:
for row in model.query.all():
output[model.__name__].append(model_to_dict(row))
print(encoder.encode(output))
decoder: Dict[str, Callable[[Any], Any]] = {
"AlarmState": lambda x: AlarmState.__getattribute__(AlarmState, x),
"AutomationState": lambda x: AutomationState.__getattribute__(AutomationState, x),
"bytes": lambda x: base64.decodebytes(x.encode('utf-8')),
2024-11-16 19:47:41 +00:00
"datetime": datetime.datetime.fromisoformat,
"int": int,
"str": lambda x: x,
2022-04-22 12:52:41 +01:00
}
def db_import_model(model: Model, data: List[Dict[str, Any]]) -> None:
for row in data:
new = model()
for col in row:
type_name, col_name = col.split("_", 1)
setattr(new, col_name, decoder.get(type_name, lambda x: x)(row[col]))
db.session.add(new)
def db_import() -> None:
data = json.load(sys.stdin)
# import order matters due to foreign key constraints
for model in models:
db_import_model(model, data.get(model.__name__, []))
db.session.commit()
2022-04-22 12:52:41 +01:00
2022-06-17 13:21:35 +01:00
class DbCliHandler(BaseCliHandler):
2022-04-22 12:52:41 +01:00
@classmethod
2022-05-16 11:44:03 +01:00
def add_subparser_to(cls, subparsers: _SubparserType) -> None:
2022-04-22 12:52:41 +01:00
parser = subparsers.add_parser("db", help="database operations")
parser.add_argument("--export", help="export data to JSON format", action="store_true")
parser.add_argument("--import", help="import data from JSON format", action="store_true")
2022-04-22 12:52:41 +01:00
parser.set_defaults(cls=cls)
2022-05-16 11:44:03 +01:00
def run(self) -> None:
2022-04-22 12:52:41 +01:00
with app.app_context():
if self.args.export:
db_export()
elif vars(self.args)["import"]:
db_import()
2022-04-22 12:52:41 +01:00
else:
logging.error("No action requested")