From 76112ad3e1c75161b9af016184b36d2328af0427 Mon Sep 17 00:00:00 2001 From: EstrellaXD Date: Wed, 14 Jun 2023 09:49:16 +0800 Subject: [PATCH] feat: new orm module --- backend/src/module/database/orm/__init__.py | 1 + backend/src/module/database/orm/connector.py | 44 +++++++++++++ backend/src/module/database/orm/delete.py | 26 ++++++++ backend/src/module/database/orm/insert.py | 43 +++++++++++++ backend/src/module/database/orm/search.py | 48 ++++++++++++++ backend/src/module/database/orm/update.py | 68 ++++++++++++++++++++ 6 files changed, 230 insertions(+) create mode 100644 backend/src/module/database/orm/__init__.py create mode 100644 backend/src/module/database/orm/connector.py create mode 100644 backend/src/module/database/orm/delete.py create mode 100644 backend/src/module/database/orm/insert.py create mode 100644 backend/src/module/database/orm/search.py create mode 100644 backend/src/module/database/orm/update.py diff --git a/backend/src/module/database/orm/__init__.py b/backend/src/module/database/orm/__init__.py new file mode 100644 index 00000000..4b56580f --- /dev/null +++ b/backend/src/module/database/orm/__init__.py @@ -0,0 +1 @@ +from .connector import Connector diff --git a/backend/src/module/database/orm/connector.py b/backend/src/module/database/orm/connector.py new file mode 100644 index 00000000..f55f4919 --- /dev/null +++ b/backend/src/module/database/orm/connector.py @@ -0,0 +1,44 @@ +import sqlite3 + +from .delete import Delete +from .insert import Insert +from .select import Select +from .update import Update + +from module.conf import DATA_PATH + + +class Connector: + def __init__(self, database: str = DATA_PATH, table_name: str = None, data: dict = None): + self._conn = sqlite3.connect(database) + self._cursor = self._conn.cursor() + self.update = Update(self, table_name, data) + self.insert = Insert(self, table_name, data) + self.select = Select(self, table_name, data) + self.delete = Delete(self, table_name, data) + + def __enter__(self): + return self + + def __exit__(self, exc_type, exc_value, traceback): + self._conn.close() + + def execute(self, sql: str, params: tuple = None): + if params is None: + self._cursor.execute(sql) + else: + self._cursor.execute(sql, params) + self._conn.commit() + + def executemany(self, sql: str, params: list[tuple]): + self._cursor.executemany(sql, params) + self._conn.commit() + + def fetchall(self): + return self._cursor.fetchall() + + def fetchone(self): + return self._cursor.fetchone() + + def fetchmany(self, size: int): + return self._cursor.fetchmany(size) \ No newline at end of file diff --git a/backend/src/module/database/orm/delete.py b/backend/src/module/database/orm/delete.py new file mode 100644 index 00000000..cf7ce81b --- /dev/null +++ b/backend/src/module/database/orm/delete.py @@ -0,0 +1,26 @@ + + +class Delete: + def __init__(self, connector: Connector, table_name: str, data: dict): + self.db = connector + self._table_name = table_name + self._data = data + + def one(self, _id: int) -> bool: + self.db.execute( + f""" + DELETE FROM {self._table_name} + WHERE id = :id + """, + {"id": _id}, + ) + return True + + def all(self): + self.db.execute( + f""" + DELETE FROM {self._table_name} + """, + ) + return True + diff --git a/backend/src/module/database/orm/insert.py b/backend/src/module/database/orm/insert.py new file mode 100644 index 00000000..8f0986b4 --- /dev/null +++ b/backend/src/module/database/orm/insert.py @@ -0,0 +1,43 @@ +from .connector import Connector + + +class Insert: + def __init__(self, db: Connector, table_name: str, data: dict): + self.db = db + self._table_name = table_name + self._columns = data.items() + + def __gen_id(self) -> int: + self.db.execute(f"SELECT MAX(id) FROM {self._table_name}") + max_id = self.db.fetchone()[0] + if max_id is None: + return 1 + return max_id + 1 + + def one(self, data: dict) -> bool: + _id = self.__gen_id() + data["id"] = _id + columns = ", ".join(data.keys()) + placeholders = ", ".join([f":{key}" for key in data.keys()]) + self.db.execute( + f""" + INSERT INTO {self._table_name} ({columns}) + VALUES ({placeholders}) + """, + data, + ) + return True + + def list(self, data: list[dict]): + columns = ", ".join(data[0].keys()) + placeholders = ", ".join([f":{key}" for key in data[0].keys()]) + self.db.executemany( + f""" + INSERT INTO {self._table_name} ({columns}) + VALUES ({placeholders}) + """, + data, + ) + return True + + diff --git a/backend/src/module/database/orm/search.py b/backend/src/module/database/orm/search.py new file mode 100644 index 00000000..f0e5d05d --- /dev/null +++ b/backend/src/module/database/orm/search.py @@ -0,0 +1,48 @@ + +class Select: + def __init__(self, connector: Connector, table_name: str, data: dict): + self._connector = connector + self._table_name = table_name + self._data = data + + def id(self, _id: int): + self._connector.execute( + f""" + SELECT * FROM {self._table_name} + WHERE id = :id + """, + {"id": _id}, + ) + return self._connector.fetchone() + + def all(self): + self._connector.execute( + f""" + SELECT * FROM {self._table_name} + """, + ) + return self._connector.fetchall() + + def one(self, keys: list[str], values: list[str]): + columns = ", ".join(keys) + placeholders = ", ".join([f":{key}" for key in keys]) + self._connector.execute( + f""" + SELECT {columns} FROM {self._table_name} + WHERE {placeholders} + """, + dict(zip(keys, values)), + ) + return self._connector.fetchone() + + def list(self, keys: list[str], values: list[str]): + columns = ", ".join(keys) + placeholders = ", ".join([f":{key}" for key in keys]) + self._connector.execute( + f""" + SELECT {columns} FROM {self._table_name} + WHERE {placeholders} + """, + dict(zip(keys, values)), + ) + return self._connector.fetchall() \ No newline at end of file diff --git a/backend/src/module/database/orm/update.py b/backend/src/module/database/orm/update.py new file mode 100644 index 00000000..2a90c9e3 --- /dev/null +++ b/backend/src/module/database/orm/update.py @@ -0,0 +1,68 @@ +import logging +from .connector import Connector + +logger = logging.getLogger(__name__) + + +class Update: + def __init__(self, db: Connector, table_name: str, data: dict): + self.db = db + self._table_name = table_name + self._columns = data.items() + + def table(self): + columns = ", ".join( + [ + f"{key} {self.__python_to_sqlite_type(value)}" + for key, value in self._columns + ] + ) + create_table_sql = f"CREATE TABLE IF NOT EXISTS {self._table_name} ({columns});" + self.db.execute(create_table_sql) + self.db.execute(f"PRAGMA table_info({self._table_name})") + existing_columns = { + column_info[1]: column_info for column_info in self.db.fetchall() + } + for key, value in self._columns: + if key not in existing_columns: + insert_column = self.__python_to_sqlite_type(value) + if value is None: + value = "NULL" + add_column_sql = f"ALTER TABLE {self._table_name} ADD COLUMN {key} {insert_column} DEFAULT {value};" + self.db.execute(add_column_sql) + logger.debug(f"Create / Update table {self._table_name}.") + + def one(self, data: dict) -> bool: + _id = data.pop("id") + set_sql = ", ".join([f"{key} = :{key}" for key in data.keys()]) + self.db.execute( + f""" + UPDATE {self._table_name} + SET {set_sql} + WHERE id = {_id} + """, + data, + ) + logger.debug(f"Update {_id} in {self._table_name}.") + return True + + def list(self, data: list[dict]): + for item in data: + self.one(item) + + @staticmethod + def __python_to_sqlite_type(value) -> str: + if isinstance(value, int): + return "INTEGER NOT NULL" + elif isinstance(value, float): + return "REAL NOT NULL" + elif isinstance(value, str): + return "TEXT NOT NULL" + elif isinstance(value, bool): + return "INTEGER NOT NULL" + elif isinstance(value, list): + return "TEXT NOT NULL" + elif value is None: + return "TEXT" + else: + raise ValueError(f"Unsupported data type: {type(value)}") \ No newline at end of file