Model: Start export model into SQLite database.

* Study: Create table.
* River: Create node and reach table.
* Geometry: Create table only for profileXYZ and pointXYZ.
results
Pierre-Antoine Rouby 2023-06-28 16:50:08 +02:00
parent 5566d87774
commit 2ed3254b46
12 changed files with 450 additions and 29 deletions

148
src/Model/DB.py Normal file
View File

@ -0,0 +1,148 @@
# -*- coding: utf-8 -*-
import os
import sqlite3
from pathlib import Path
from tools import SQL
from Model.Except import NotImplementedMethodeError
# Top level model class
class SQLModel(SQL):
_sub_classes = []
def _init_db_file(self, db, is_new = True):
exists = Path(db).exists()
if exists and is_new:
os.remove(db)
self._db = sqlite3.connect(db)
self._cur = self._db.cursor()
if is_new:
self._create() # Create db
self._save() # Save
else:
self._update() # Update db scheme if necessary
self._load() # Load data
def _create_submodel(self):
for cls in self._sub_classes:
requests = cls._sql_create(
lambda sql: self.execute(
sql,
fetch_one = False,
commit = True
)
)
def _create(self):
raise NotImplementedMethodeError(self, self._create)
def _update_submodel(self, version):
for cls in self._sub_classes:
requests = cls._sql_update(
lambda sql: self.execute(
sql,
fetch_one = False,
commit = True
),
version
)
def _update(self):
raise NotImplementedMethodeError(self, self._update)
def _save_submodel(self, objs):
for obj in objs:
requests = obj._sql_save(
lambda sql: self.execute(
sql,
fetch_one = False,
commit = True
)
)
def _save(self):
raise NotImplementedMethodeError(self, self._save)
@classmethod
def _load(cls, filename):
raise NotImplementedMethodeError(cls, cls._load)
# Sub model class
class SQLSubModel(object):
_sub_classes = []
def _sql_format(self, value):
# Replace ''' by ''' to preserve SQL injection
if type(value) == str:
value = value.replace("'", "'")
return value
@classmethod
def _create_submodel(cls, execute):
for sc in cls._sub_classes:
sc._sql_create(execute)
@classmethod
def _sql_create(cls, execute):
"""Create data base scheme
Args:
execute: Function to exec SQL resquest
Returns:
Return true, otherelse false if an issue appear
"""
raise NotImplementedMethodeError(cls, cls._sql_create)
@classmethod
def _update_submodel(cls, execute, version):
for sc in cls._sub_classes:
sc._sql_update(execute, version)
@classmethod
def _sql_update(cls, execute, version):
"""Update data base scheme
Args:
execute: Function to exec SQL resquest
version: Current database version
Returns:
Return true, otherelse false if an issue appear
"""
raise NotImplementedMethodeError(cls, cls._sql_update)
@classmethod
def _sql_load(cls, execute, data = None):
"""Load instance of this class from SQL data base
Args:
execute: Function to exec SQL request
data: Optional data for the class constructor
Returns:
Return new instance of class
"""
raise NotImplementedMethodeError(cls, cls._sql_load)
def _save_submodel(self, execute):
for sc in self._sub_classes:
sc._sql_update(execute)
def _sql_save(self, execute, data = None):
"""Save class data to data base
Args:
execute: Function to exec SQL resquest
data: Optional additional information for save
Returns:
Return true, otherelse false if an issue appear during
save
"""
raise NotImplementedMethodeError(self, self._sql_save)

View File

@ -70,7 +70,7 @@ class NotImplementedMethodeError(ExeceptionWithMessageBox):
f" '{self.func.__name__}' " + f" '{self.func.__name__}' " +
_translate("Exception", "not implemented") + _translate("Exception", "not implemented") +
_translate("Exception", "for class") + _translate("Exception", "for class") +
f" '{self.obj.__class__}'" f" '{self.obj.__class__ if self.obj.__class__ != type else self.obj}'"
) )
def header(self): def header(self):

View File

@ -3,9 +3,12 @@
from math import dist from math import dist
import numpy as np import numpy as np
from Model.DB import SQLSubModel
from Model.Geometry.Point import Point from Model.Geometry.Point import Point
class PointXYZ(Point): class PointXYZ(Point, SQLSubModel):
_sub_classes = []
def __init__(self, x:float = 0.0, y:float = 0.0, z:float = 0.0, def __init__(self, x:float = 0.0, y:float = 0.0, z:float = 0.0,
name:str = "", status = None): name:str = "", status = None):
super(PointXYZ, self).__init__(name=name, status=status) super(PointXYZ, self).__init__(name=name, status=status)
@ -14,6 +17,36 @@ class PointXYZ(Point):
self._y = float(y) self._y = float(y)
self._z = float(z) self._z = float(z)
@classmethod
def _sql_create(cls, execute):
execute("""
CREATE TABLE geometry_pointXYZ(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT,
x INTEGER NOT NULL,
y INTEGER NOT NULL,
z INTEGER NOT NULL,
profile INTEGER NOT NULL,
FOREIGN KEY(profile) REFERENCES profileXYZ(id)
)
""")
cls._create_submodel(execute)
return True
@classmethod
def _sql_update(cls, execute, version):
cls._update_submodel(execute, version)
return True
@classmethod
def _sql_load(cls, execute, data = None):
return None
def _sql_save(self, execute, data = None):
return True
@classmethod @classmethod
def from_data(cls, header, data): def from_data(cls, header, data):
point = None point = None

View File

@ -5,12 +5,17 @@ from typing import List
from tools import timer from tools import timer
from Model.DB import SQLSubModel
from Model.Except import ClipboardFormatError from Model.Except import ClipboardFormatError
from Model.Geometry.Profile import Profile from Model.Geometry.Profile import Profile
from Model.Geometry.PointXYZ import PointXYZ from Model.Geometry.PointXYZ import PointXYZ
from Model.Geometry.Vector_1d import Vector1d from Model.Geometry.Vector_1d import Vector1d
class ProfileXYZ(Profile): class ProfileXYZ(Profile, SQLSubModel):
_sub_classes = [
PointXYZ,
]
def __init__(self, def __init__(self,
name: str = "", name: str = "",
kp: float = 0., kp: float = 0.,
@ -41,6 +46,36 @@ class ProfileXYZ(Profile):
status = status, status = status,
) )
@classmethod
def _sql_create(cls, execute):
execute("""
CREATE TABLE geometry_profileXYZ(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT,
reach INTEGER NOT NULL,
kp REAL NOT NULL,
num INTEGER NOT NULL,
code1 INTEGER NOT NULL,
code2 INTEGER NOT NULL,
FOREIGN KEY(reach) REFERENCES river_reach(id)
)
""")
cls._create_submodel(execute)
return True
@classmethod
def _sql_update(cls, execute, version):
cls._update_submodel(execute, version)
return True
@classmethod
def _sql_load(cls, execute, data = None):
return None
def _sql_save(self, execute, data = None):
return True
@classmethod @classmethod
def from_data(cls, header, data): def from_data(cls, header, data):
profile = None profile = None

View File

@ -10,12 +10,18 @@ from functools import reduce
from tools import flatten, timer, trace from tools import flatten, timer, trace
from Model.DB import SQLSubModel
from Model.Geometry.Profile import Profile from Model.Geometry.Profile import Profile
from Model.Geometry.ProfileXYZ import ProfileXYZ from Model.Geometry.ProfileXYZ import ProfileXYZ
from Model.Except import FileFormatError, exception_message_box from Model.Except import FileFormatError, exception_message_box
class Reach: class Reach(SQLSubModel):
_sub_classes = [
ProfileXYZ,
]
def __init__(self, status=None, parent=None): def __init__(self, status=None, parent=None):
self._status = status self._status = status
self._parent = parent self._parent = parent
@ -24,6 +30,34 @@ class Reach:
self._guidelines_is_valid = False self._guidelines_is_valid = False
self._guidelines = {} self._guidelines = {}
@classmethod
def _sql_create(cls, execute):
cls._create_submodel(execute)
return True
@classmethod
def _sql_update(cls, execute, version):
cls._update_submodel(execute, version)
return None
@classmethod
def _sql_load(cls, execute, data = None):
new = cls(status = data["status"], parent = data["parent"])
new._profiles = ProfileXYZ._sql_load(
execute,
data = {
"status": data["status"],
"reach": new,
}
)
return new
def _sql_save(self, execute, data = None):
cls._save_submodel(execute, data)
return True
def profile(self, i): def profile(self, i):
"""Returns profile at index i """Returns profile at index i

View File

@ -38,6 +38,15 @@ class Node(object):
def name(self): def name(self):
return self._name return self._name
@property
def x(self):
return self.pos.x
@property
def y(self):
return self.pos.y
def setPos(self, x, y): def setPos(self, x, y):
self.pos.x = x self.pos.x = x
self.pos.y = y self.pos.y = y

View File

@ -1,5 +1,7 @@
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
from Model.DB import SQLSubModel
from Model.Network.Node import Node from Model.Network.Node import Node
from Model.Network.Edge import Edge from Model.Network.Edge import Edge
from Model.Network.Graph import Graph from Model.Network.Graph import Graph
@ -16,7 +18,9 @@ from Model.SolverParameters.SolverParametersList import SolverParametersList
from Solver.Solvers import solver_type_list from Solver.Solvers import solver_type_list
class RiverNode(Node): class RiverNode(Node, SQLSubModel):
_sub_classes = []
def __init__(self, id:str, name:str, def __init__(self, id:str, name:str,
x:float, y:float, x:float, y:float,
status = None): status = None):
@ -28,6 +32,31 @@ class RiverNode(Node):
self._locker = None self._locker = None
@classmethod
def _sql_create(cls, execute):
execute("""
CREATE TABLE river_node(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
x REAL NOT NULL,
y REAL NOT NULL
)
""")
cls._create_submodel(execute)
return True
@classmethod
def _sql_update(cls, execute, version):
return None
@classmethod
def _sql_load(cls, execute, data = None):
return True
def _sql_save(self, execute, data = None):
return True
@property @property
def locker(self): def locker(self):
return self._locker return self._locker
@ -37,7 +66,12 @@ class RiverNode(Node):
self._locker = locker self._locker = locker
class RiverReach(Edge): class RiverReach(Edge, SQLSubModel):
_sub_classes = [
Reach,
# SectionList,
]
def __init__(self, id:str, name:str, def __init__(self, id:str, name:str,
node1:RiverNode = None, node1:RiverNode = None,
node2:RiverNode = None, node2:RiverNode = None,
@ -51,6 +85,33 @@ class RiverReach(Edge):
self._reach = Reach(status=self._status, parent=self) self._reach = Reach(status=self._status, parent=self)
self._sections = SectionList(status=self._status) self._sections = SectionList(status=self._status)
@classmethod
def _sql_create(cls, execute):
execute("""
CREATE TABLE river_reach(
id INTEGER NOT NULL PRIMARY KEY,
name TEXT NOT NULL,
node1 INTEGER,
node2 INTEGER,
FOREIGN KEY(node1) REFERENCES river_node(id),
FOREIGN KEY(node2) REFERENCES river_node(id)
)
""")
cls._create_submodel(execute)
return True
@classmethod
def _sql_update(cls, execute, version):
return True
@classmethod
def _sql_load(cls, execute, data = None):
return None
def _sql_save(self, execute, data = None):
return True
@property @property
def reach(self): def reach(self):
return self._reach return self._reach
@ -59,7 +120,17 @@ class RiverReach(Edge):
def sections(self): def sections(self):
return self._sections return self._sections
class River(Graph): class River(Graph, SQLSubModel):
_sub_classes = [
RiverNode,
RiverReach,
# BoundaryConditionList,
# LateralContributionList,
# InitialConditionsDict,
# StricklersList,
# SolverParametersList,
]
def __init__(self, status=None): def __init__(self, status=None):
super(River, self).__init__(status=status) super(River, self).__init__(status=status)
@ -74,6 +145,31 @@ class River(Graph):
self._stricklers = StricklersList(status=self._status) self._stricklers = StricklersList(status=self._status)
self._parameters = {} self._parameters = {}
@classmethod
def _sql_create(cls, execute):
cls._create_submodel(execute)
return True
@classmethod
def _sql_update(cls, execute, version):
return True
@classmethod
def _sql_load(cls, execute, data = None):
return None
def _sql_save(self, execute, data = None):
return True
@property
def reach(self):
return self._reach
@property
def sections(self):
return self._sections
@property @property
def boundary_condition(self): def boundary_condition(self):
return self._boundary_condition return self._boundary_condition

View File

@ -3,8 +3,8 @@
import pickle import pickle
class Serializable(): class Serializable():
def __init__(self, filename): def __init__(self):
self.filename = filename return
@classmethod @classmethod
def open(cls, filename): def open(cls, filename):

View File

@ -3,18 +3,28 @@
import os import os
from datetime import datetime from datetime import datetime
from Model.DB import SQLModel
from Model.Saved import SavedStatus from Model.Saved import SavedStatus
from Model.Serializable import Serializable from Model.Serializable import Serializable
from Model.Except import NotImplementedMethodeError
from Model.River import River from Model.River import River
from Checker.Study import * from Checker.Study import *
class Study(Serializable): class Study(SQLModel):
def __init__(self): _sub_classes = [
# Serialization information River,
super(Study, self).__init__("") ]
self.filename = ""
def __init__(self, filename = None, init_new = True):
# Metadata
self._version = "0.0.0"
self.creation_date = datetime.now()
self.last_modification_date = datetime.now()
self.last_save_date = datetime.now()
self._filename = filename
super(Study, self).__init__(filename = filename)
self.status = SavedStatus() self.status = SavedStatus()
@ -25,10 +35,7 @@ class Study(Serializable):
self._time_system = "time" self._time_system = "time"
self._date = datetime.fromtimestamp(0) self._date = datetime.fromtimestamp(0)
self.creation_date = datetime.now() if init_new:
self.last_modification_date = datetime.now()
self.last_save_date = datetime.now()
# Study data # Study data
self._river = River(status = self.status) self._river = River(status = self.status)
@ -66,6 +73,15 @@ class Study(Serializable):
self._name = str(name) self._name = str(name)
self.status.modified() self.status.modified()
@property
def filename(self):
return self._filename
@filename.setter
def filename(self, filename):
self._filename = str(filename)
self._init_db_file(filename, is_new = True)
@property @property
def time_system(self): def time_system(self):
return self._time_system return self._time_system
@ -88,9 +104,9 @@ class Study(Serializable):
self._date = timestamp self._date = timestamp
self.status.modified() self.status.modified()
@classmethod # @classmethod
def new(cls): # def new(cls):
return cls() # return cls()
@classmethod @classmethod
def new(cls, name, description, date = None): def new(cls, name, description, date = None):
@ -101,3 +117,46 @@ class Study(Serializable):
me.use_date() me.use_date()
me.date = date me.date = date
return me return me
@classmethod
def open(cls, filename):
me = cls._load(filename)
return me
#######
# SQL #
#######
def _create(self):
# Info (metadata)
self.execute("CREATE TABLE info(key TEXT NOT NULL UNIQUE, value TEXT NOT NULL)")
self.execute(
f"INSERT INTO info VALUES ('version', '{self._sql_format(self._version)}')",
commit = True
)
self._create_submodel()
self.commit()
def _update(self):
version = self.execute(f"SELECT value FROM info WHERE key='version'")
print(f"{version} == {self._version}")
if version == self._version:
return True
print("TODO: update")
raise NotImplementedMethodeError(self, self._update)
@classmethod
def _load(cls, filename):
new = cls(init_new = False)
# Load river data
self._river = River.load()
return new
def _save(self):
self.commit()

View File

@ -213,7 +213,7 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
Returns: Returns:
Nothing Nothing
""" """
if self.model.filename == "": if self.model.filename is None or self.model.filename == "":
file_name, _ = QFileDialog.getSaveFileName( file_name, _ = QFileDialog.getSaveFileName(
self, "Save File", self, "Save File",
"", "Pamhyr(*.pkl)" "", "Pamhyr(*.pkl)"
@ -282,9 +282,9 @@ class ApplicationWindow(QMainWindow, ListedSubWindow, WindowToolKit):
if self.model is None: if self.model is None:
dialog = QFileDialog(self) dialog = QFileDialog(self)
dialog.setFileMode(QFileDialog.FileMode.ExistingFile) dialog.setFileMode(QFileDialog.FileMode.ExistingFile)
dialog.setDefaultSuffix(".pkl") dialog.setDefaultSuffix(".pamhyr")
#dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden) #dialog.setFilter(dialog.filter() | QtCore.QDir.Hidden)
dialog.setNameFilters(['PamHyr (*.pkl)']) dialog.setNameFilters(['PamHyr (*.pamhyr)'])
if dialog.exec_(): if dialog.exec_():
file_name = dialog.selectedFiles() file_name = dialog.selectedFiles()

View File

@ -19,7 +19,7 @@ class Config(SQL):
self.filename = Config.filename() self.filename = Config.filename()
self.set_default_value() self.set_default_value()
super(Config, self).__init__(db = self.filename) super(Config, self).__init__(filename = self.filename)
def _create(self): def _create(self):
# Info (meta data) # Info (meta data)

View File

@ -165,7 +165,7 @@ def old_pamhyr_date_to_timestamp(date:str):
# from sqlite3. # from sqlite3.
class SQL(object): class SQL(object):
def __init__(self, db = "db.sqlite3"): def _init_db_file(self, db):
exists = Path(db).exists() exists = Path(db).exists()
self._db = sqlite3.connect(db) self._db = sqlite3.connect(db)
@ -178,6 +178,13 @@ class SQL(object):
self._update() # Update db scheme if necessary self._update() # Update db scheme if necessary
self._load() # Load data self._load() # Load data
def __init__(self, filename = None):
self._db = None
if filename is not None:
self._init_db_file(filename)
def commit(self): def commit(self):
self._db.commit() self._db.commit()