refactor: create TableService child class from SqliteService

This commit is contained in:
thomasabishop 2024-12-26 16:32:43 +00:00
parent 808fb54db5
commit 428f1c435f
7 changed files with 96 additions and 82 deletions

BIN
db/eolas.db Normal file

Binary file not shown.

View file

@ -8,7 +8,7 @@ setup(
install_requires=["python-frontmatter", "termcolor"], install_requires=["python-frontmatter", "termcolor"],
entry_points={ entry_points={
"console_scripts": [ "console_scripts": [
"run=app:main", "eolas-db=cli:main",
], ],
}, },
) )

View file

@ -4,13 +4,13 @@ from constants import EOLAS_DIRECTORY
from controllers.controller import Controller from controllers.controller import Controller
from services.database_service import DatabaseService from services.database_service import DatabaseService
from services.parse_file_service import ParseFileService from services.parse_file_service import ParseFileService
from services.sqlite_service import SqliteService from services.table_service import TableService
database_service = DatabaseService("eolas") database_service = DatabaseService("eolas")
database_connection = database_service.connect() database_connection = database_service.connect()
sqlite_service = SqliteService(database_connection) table_service = TableService(database_connection)
parse_file_service = ParseFileService(EOLAS_DIRECTORY) parse_file_service = ParseFileService(EOLAS_DIRECTORY)
controller = Controller(database_service, sqlite_service, parse_file_service) controller = Controller(database_service, table_service, parse_file_service)
def main(): def main():
@ -18,11 +18,11 @@ def main():
prog="eolas-db", description="Eolas database manager." prog="eolas-db", description="Eolas database manager."
) )
parser.add_argument( parser.add_argument(
"command", choices=["parse", "populate"], help="Command to execute" "command", choices=["populate-database"], help="Command to execute"
) )
args = parser.parse_args() args = parser.parse_args()
if args.command == "populate": if args.command == "populate-database":
controller.populate_database() controller.populate_database()

View file

@ -1,12 +1,12 @@
class Controller: class Controller:
def __init__(self, database_service, sqlite_service, parse_file_service): def __init__(self, database_service, table_service, parse_file_service):
self.database_service = database_service self.database_service = database_service
self.sqlite_service = sqlite_service self.table_service = table_service
self.parse_file_service = parse_file_service self.parse_file_service = parse_file_service
def populate_database(self): def populate_database(self):
try: try:
entries = self.parse_file_service.parse_source_directory() entries = self.parse_file_service.parse_source_directory()
self.sqlite_service.populate_tables(entries) self.table_service.populate_tables(entries)
finally: finally:
self.database_service.disconnect() self.database_service.disconnect()

View file

@ -1,3 +1,4 @@
import os
import sqlite3 import sqlite3
from typing import Optional from typing import Optional
@ -15,6 +16,9 @@ class DatabaseService:
return self.connection return self.connection
try: try:
if not os.path.exists(self.db_path):
os.makedirs(self.db_path)
print(colored("INFO Created database directory", "light_blue"))
self.connection = sqlite3.connect(f"{self.db_path}/{self.db_name}.db") self.connection = sqlite3.connect(f"{self.db_path}/{self.db_name}.db")
self.connection.execute("PRAGMA foreign_keys = ON") self.connection.execute("PRAGMA foreign_keys = ON")
print(colored("INFO Database connection established", "light_blue")) print(colored("INFO Database connection established", "light_blue"))

View file

@ -8,11 +8,11 @@ from sql.create_tables import tables
class SqliteService: class SqliteService:
def __init__(self, connection): def __init__(self, db_connection):
self.connection = connection self.connection = db_connection
self.cursor = connection.cursor() self.cursor = db_connection.cursor()
def __query(self, sql, params=None, errorMessage: Optional[str] = None): def _query(self, sql, params=None, errorMessage: Optional[str] = None):
try: try:
if params: if params:
self.cursor.execute(sql, params) self.cursor.execute(sql, params)
@ -24,72 +24,3 @@ class SqliteService:
if errorMessage: if errorMessage:
raise Exception(f"ERROR {errorMessage}: {e}") raise Exception(f"ERROR {errorMessage}: {e}")
raise raise
def __create_tables(self):
for table in tables:
self.__query(
table["create_statement"],
errorMessage=f"Problem creating table {table['name']}",
)
print(colored("INFO Created tables", "light_blue"))
def __drop_tables(self):
# Reverse the order of `tables` list to avoid foreign key violation when
# deleting
for table in reversed(tables):
self.__query(
f"DROP TABLE IF EXISTS {table['name']}",
errorMessage=f"Problem truncating table {table['name']}",
)
print(colored("INFO Cleared tables", "light_blue"))
def __entry_exists(self, title) -> bool:
self.__query("SELECT 1 FROM entries WHERE title = :title", {"title": title})
result = self.cursor.fetchone()
return result is not None
def __populate_base_tables(self, entries: list[Entry]):
for entry in entries:
self.__query(
"INSERT INTO entries (title, last_modified, size, body) VALUES (:title, :last_modified, :size, :body)",
entry,
errorMessage=f"The following entry could not be added to `entries` table: {entry}",
)
tags = entry.get("tags")
if tags:
for tag in tags:
self.__query(
"INSERT OR IGNORE INTO tags (name) VALUES (:tag_name)",
{"tag_name": tag},
)
print(colored("INFO Base tables populated", "light_blue"))
def __populate_junction_tables(self, entries: list[Entry]):
for entry in entries:
tags = entry.get("tags")
links = entry.get("links")
if tags:
for tag in tags:
self.__query(
"INSERT INTO entries_tags (entry_title, tag_name) VALUES (:entry_title, :tag_name)",
{"entry_title": entry.get("title"), "tag_name": tag},
)
if links:
for link in links:
if self.__entry_exists(link):
self.__query(
"INSERT OR IGNORE INTO backlinks (source_entry_title, target_entry_title) VALUES (:source_entry_title, :target_entry_title)",
{
"source_entry_title": entry.get("title"),
"target_entry_title": link,
},
)
print(colored("INFO Junction tables populated", "light_blue"))
def populate_tables(self, entries: list[Entry]):
self.__drop_tables()
self.__create_tables()
self.__populate_base_tables(entries)
self.__populate_junction_tables(entries)

View file

@ -0,0 +1,79 @@
from termcolor import colored
from models.entry import Entry
from services.sqlite_service import SqliteService
from sql.create_tables import tables
class TableService(SqliteService):
def __init__(self, db_connection):
super().__init__(db_connection)
def __create_tables(self):
for table in tables:
self._query(
table["create_statement"],
errorMessage=f"Problem creating table {table['name']}",
)
print(colored("INFO Created tables", "light_blue"))
def __drop_tables(self):
# Reverse the order of `tables` list to avoid foreign key violation when
# deleting
for table in reversed(tables):
self._query(
f"DROP TABLE IF EXISTS {table['name']}",
errorMessage=f"Problem truncating table {table['name']}",
)
print(colored("INFO Cleared tables", "light_blue"))
def __entry_exists(self, title) -> bool:
self._query("SELECT 1 FROM entries WHERE title = :title", {"title": title})
result = self.cursor.fetchone()
return result is not None
def __populate_base_tables(self, entries: list[Entry]):
for entry in entries:
self._query(
"INSERT INTO entries (title, last_modified, size, body) VALUES (:title, :last_modified, :size, :body)",
entry,
errorMessage=f"The following entry could not be added to `entries` table: {entry}",
)
tags = entry.get("tags")
if tags:
for tag in tags:
self._query(
"INSERT OR IGNORE INTO tags (name) VALUES (:tag_name)",
{"tag_name": tag},
)
print(colored("INFO Base tables populated", "light_blue"))
def __populate_junction_tables(self, entries: list[Entry]):
for entry in entries:
tags = entry.get("tags")
links = entry.get("links")
if tags:
for tag in tags:
self._query(
"INSERT INTO entries_tags (entry_title, tag_name) VALUES (:entry_title, :tag_name)",
{"entry_title": entry.get("title"), "tag_name": tag},
)
if links:
for link in links:
if self.__entry_exists(link):
self._query(
"INSERT OR IGNORE INTO backlinks (source_entry_title, target_entry_title) VALUES (:source_entry_title, :target_entry_title)",
{
"source_entry_title": entry.get("title"),
"target_entry_title": link,
},
)
print(colored("INFO Junction tables populated", "light_blue"))
def populate_tables(self, entries: list[Entry]):
self.__drop_tables()
self.__create_tables()
self.__populate_base_tables(entries)
self.__populate_junction_tables(entries)