[main] Added imports as well a whole bunch of tweaks elsewhere
This commit is contained in:
parent
142b2d449d
commit
48163325d8
@ -9,5 +9,5 @@ python-dotenv==1.0.1
|
||||
shiboken2==5.13.2
|
||||
shiboken6==6.8.1
|
||||
SQLAlchemy==2.0.36
|
||||
sqlalchemy-stubs==0.4
|
||||
typing_extensions==4.12.2
|
||||
xmlschema==3.4.3
|
19
src/app.py
19
src/app.py
@ -7,7 +7,8 @@ from PySide6.QtWidgets import QMessageBox, QApplication
|
||||
|
||||
from utils.config import UserConfig
|
||||
from utils.database import DatabaseManager
|
||||
from utils.errors.database_config_error import DatabaseConfigError
|
||||
from utils.errors.database.database_config_error import DatabaseConfigError
|
||||
from utils.errors.database.database_connection_error import DatabaseConnectionError
|
||||
from utils.setup_logger import setup_logger
|
||||
|
||||
from ui.window import LibraryWindow
|
||||
@ -25,15 +26,18 @@ class LibraryApp():
|
||||
try:
|
||||
self.database_manger = DatabaseManager()
|
||||
except DatabaseConfigError as e:
|
||||
detail_text = f"Invalid config: {e.config_name}"
|
||||
self.show_error(e.message, detail_text=detail_text)
|
||||
sys.exit(1)
|
||||
self.exit_with_error(f"Invalid config: {e.config_name}", e.message)
|
||||
except DatabaseConnectionError as e:
|
||||
self.exit_with_error(f"Could not connect to database: {e}")
|
||||
except FileNotFoundError:
|
||||
self.show_error("Configuration not found")
|
||||
sys.exit(1)
|
||||
|
||||
self.exit_with_error("Configuration not found")
|
||||
self.window = LibraryWindow()
|
||||
|
||||
def exit_with_error(self, error: str, additional_text: str = ""):
|
||||
self.show_error(error, additional_text)
|
||||
self.qt_app.quit()
|
||||
sys.exit(1)
|
||||
|
||||
def run(self) -> int:
|
||||
self.window.show()
|
||||
status = self.qt_app.exec()
|
||||
@ -53,6 +57,7 @@ class LibraryApp():
|
||||
|
||||
def cleanup(self) -> None:
|
||||
self.logger.info("Cleaning up")
|
||||
self.qt_app.quit()
|
||||
self.database_manger.cleanup()
|
||||
|
||||
|
||||
|
@ -8,56 +8,69 @@ from utils.errors.no_export_entity_error import NoExportEntityError
|
||||
|
||||
from models.book import Book
|
||||
|
||||
|
||||
class BookExporter():
|
||||
def save_xml(self, file_path: str):
|
||||
|
||||
xml = self._get_full_xml()
|
||||
|
||||
if xml is None:
|
||||
raise NoExportEntityError("No books found to export")
|
||||
|
||||
with open(file_path, "w", encoding="utf-8") as file:
|
||||
file.write(xml)
|
||||
file.write(xml)
|
||||
|
||||
|
||||
def _get_full_xml(self) -> Optional[str]:
|
||||
def _get_full_xml(self) -> str:
|
||||
root = ET.Element("books")
|
||||
|
||||
with DatabaseManager().get_session() as session:
|
||||
with DatabaseManager.get_session() as session:
|
||||
self.books = session.query(Book).all()
|
||||
|
||||
|
||||
if not self.books:
|
||||
return None
|
||||
raise NoExportEntityError("No books found to export")
|
||||
|
||||
for book in self.books:
|
||||
# Create a <book> element
|
||||
book_element = ET.SubElement(root, "book")
|
||||
|
||||
|
||||
# Add <title>
|
||||
title_element = ET.SubElement(book_element, "title")
|
||||
title_element.text = book.title
|
||||
|
||||
|
||||
# Add <author>
|
||||
author_element = ET.SubElement(book_element, "author")
|
||||
|
||||
# Add <first_name>
|
||||
author_first_name_element = ET.SubElement(
|
||||
author_element, "first_name")
|
||||
author_first_name_element.text = book.author.first_name
|
||||
|
||||
author_last_name_element = ET.SubElement(
|
||||
author_element, "last_name")
|
||||
author_last_name_element.text = book.author.last_name
|
||||
|
||||
# Add <description>
|
||||
description_element = ET.SubElement(book_element, "description")
|
||||
description_element = ET.SubElement(
|
||||
book_element, "description")
|
||||
description_element.text = book.description
|
||||
|
||||
|
||||
# Add <year_published>
|
||||
year_published_element = ET.SubElement(book_element, "year_published")
|
||||
year_published_element = ET.SubElement(
|
||||
book_element, "year_published")
|
||||
year_published_element.text = book.year_published
|
||||
|
||||
|
||||
# Add <isbn>
|
||||
isbn_element = ET.SubElement(book_element, "isbn")
|
||||
isbn_element.text = book.isbn
|
||||
|
||||
|
||||
# Add <categories>
|
||||
categories_element = ET.SubElement(book_element, "categories")
|
||||
for category in book.categories:
|
||||
category_element = ET.SubElement(categories_element, "category")
|
||||
category_element = ET.SubElement(
|
||||
categories_element, "category")
|
||||
category_element.text = category.name
|
||||
|
||||
|
||||
# Convert the tree to a string
|
||||
tree_str = ET.tostring(root, encoding="unicode")
|
||||
|
||||
|
||||
# Pretty print the XML
|
||||
pretty_xml = minidom.parseString(tree_str).toprettyxml(indent=(" " * 4))
|
||||
return pretty_xml
|
||||
pretty_xml = minidom.parseString(
|
||||
tree_str).toprettyxml(indent=(" " * 4))
|
||||
return pretty_xml
|
||||
|
0
src/importer/__init__.py
Normal file
0
src/importer/__init__.py
Normal file
0
src/importer/book/__init__.py
Normal file
0
src/importer/book/__init__.py
Normal file
@ -15,6 +15,26 @@
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="author"> <!-- Author -->
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="first_name">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="50"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
<xs:element name="last_name">
|
||||
<xs:simpleType>
|
||||
<xs:restriction base="xs:string">
|
||||
<xs:maxLength value="50"/>
|
||||
</xs:restriction>
|
||||
</xs:simpleType>
|
||||
</xs:element>
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
||||
<xs:element name="description" type="xs:string" /> <!-- Description -->
|
||||
<xs:element name="year_published"> <!-- Year published -->
|
||||
<xs:simpleType>
|
||||
@ -33,7 +53,8 @@
|
||||
<xs:element name="categories"> <!-- Categories list -->
|
||||
<xs:complexType>
|
||||
<xs:sequence>
|
||||
<xs:element name="category" type="xs:string" maxOccurs="unbounded" />
|
||||
<xs:element name="category" type="xs:string"
|
||||
maxOccurs="unbounded" />
|
||||
</xs:sequence>
|
||||
</xs:complexType>
|
||||
</xs:element>
|
119
src/importer/book/book_importer.py
Normal file
119
src/importer/book/book_importer.py
Normal file
@ -0,0 +1,119 @@
|
||||
from typing import List
|
||||
import os
|
||||
import logging
|
||||
from xml.etree import ElementTree as ET
|
||||
from xmlschema import XMLSchema
|
||||
from utils.database import DatabaseManager
|
||||
from utils.errors.import_error.xsd_scheme_not_found import XsdSchemeNotFoundError
|
||||
from utils.errors.import_error.invalid_contents_error import InvalidContentsError
|
||||
from utils.errors.import_error.import_error import ImportError
|
||||
from models.book import Book
|
||||
from models.author import Author
|
||||
from models.book_category import BookCategory
|
||||
from sqlalchemy.exc import IntegrityError
|
||||
|
||||
class BookImporter:
|
||||
def __init__(self):
|
||||
self.logger = logging.getLogger(__name__)
|
||||
try:
|
||||
self.logger.debug("Opening XSD scheme in ./")
|
||||
scheme_path = os.path.join(os.path.dirname(__file__), "book_import_scheme.xsd")
|
||||
self.schema = XMLSchema(scheme_path)
|
||||
except Exception as e:
|
||||
self.logger.error("Failed to load XSD scheme")
|
||||
raise XsdSchemeNotFoundError(f"Failed to load XSD schema: {e}")
|
||||
|
||||
def parse_xml(self, file_path: str) -> List[Book]:
|
||||
"""Parses the XML file and validates it against the XSD schema."""
|
||||
try:
|
||||
tree = ET.parse(file_path)
|
||||
root = tree.getroot()
|
||||
|
||||
if not self.schema.is_valid(file_path):
|
||||
raise InvalidContentsError("XML file is not valid according to XSD schema.")
|
||||
|
||||
books = []
|
||||
for book_element in root.findall("book"):
|
||||
title = book_element.find("title").text
|
||||
year_published = book_element.find("year_published").text
|
||||
description = book_element.find("description").text
|
||||
isbn = book_element.find("isbn").text
|
||||
|
||||
# Parse author
|
||||
author_element = book_element.find("author")
|
||||
first_name = author_element.find("first_name").text
|
||||
last_name = author_element.find("last_name").text
|
||||
author = Author(first_name=first_name, last_name=last_name)
|
||||
|
||||
# Parse categories
|
||||
category_elements = book_element.find("categories").findall("category")
|
||||
categories = [BookCategory(name=category_element.text) for category_element in category_elements]
|
||||
|
||||
# Create a Book object
|
||||
book = Book(
|
||||
title=title,
|
||||
description=description,
|
||||
year_published=year_published,
|
||||
isbn=isbn,
|
||||
author=author,
|
||||
categories=categories
|
||||
)
|
||||
books.append(book)
|
||||
|
||||
return books
|
||||
except ET.ParseError as e:
|
||||
raise ImportError(f"Failed to parse XML file: {e}")
|
||||
|
||||
def filter_new_books(self, books: List[Book]) -> List[Book]:
|
||||
"""Filters out books that already exist in the database."""
|
||||
new_books = []
|
||||
with DatabaseManager.get_session() as session:
|
||||
for book in books:
|
||||
existing_book = session.query(Book).filter(
|
||||
Book.isbn == book.isbn,
|
||||
).first()
|
||||
if existing_book is None:
|
||||
new_books.append(book)
|
||||
return new_books
|
||||
|
||||
def save_books(self, books: List[Book]):
|
||||
"""Saves a list of books to the database."""
|
||||
try:
|
||||
with DatabaseManager.get_session() as session:
|
||||
for book in books:
|
||||
# Check if the author exists, otherwise add
|
||||
existing_author = session.query(Author).filter_by(
|
||||
first_name=book.author.first_name,
|
||||
last_name=book.author.last_name
|
||||
).first()
|
||||
if existing_author:
|
||||
book.author = existing_author
|
||||
else:
|
||||
session.add(book.author)
|
||||
session.commit()
|
||||
|
||||
# Handle categories
|
||||
new_categories = []
|
||||
for category in book.categories:
|
||||
existing_category = session.query(BookCategory).filter_by(name=category.name).first()
|
||||
if existing_category:
|
||||
new_categories.append(existing_category)
|
||||
else:
|
||||
self.logger.debug(f"Adding new category: {category.name}")
|
||||
session.add(category)
|
||||
session.commit()
|
||||
new_categories.append(category)
|
||||
# Replace book categories with the resolved categories
|
||||
book.categories = new_categories
|
||||
|
||||
# Check if the book already exists
|
||||
existing_book = session.query(Book).filter_by(isbn=book.isbn).first()
|
||||
if not existing_book:
|
||||
session.add(book)
|
||||
else:
|
||||
self.logger.warning(f"Book with ISBN {book.isbn} already exists. Skipping.")
|
||||
|
||||
# Commit all changes
|
||||
session.commit()
|
||||
except IntegrityError as e:
|
||||
raise ImportError(f"An error occurred when importing books: {e}") from e
|
@ -11,5 +11,5 @@ class BookCategoryLink(Base):
|
||||
book_id = Column(Integer, ForeignKey('book.id'), primary_key=True)
|
||||
book_category_id = Column(Integer, ForeignKey('book_category.id'), primary_key=True)
|
||||
|
||||
book = relationship('Book')
|
||||
book_category = relationship('BookCategory')
|
||||
book = relationship('Book', overlaps='categories,books')
|
||||
book_category = relationship('BookCategory', overlaps='categories,books')
|
7
src/requirements.txt
Normal file
7
src/requirements.txt
Normal file
@ -0,0 +1,7 @@
|
||||
PySide6==6.8.1
|
||||
PySide6==6.8.1.1
|
||||
PySide6_Addons==6.8.1
|
||||
PySide6_Essentials==6.8.1
|
||||
python-dotenv==1.0.1
|
||||
SQLAlchemy==2.0.36
|
||||
xmlschema==3.4.3
|
@ -89,13 +89,16 @@ class BookCard(QWidget):
|
||||
|
||||
action_edit_book = context_menu.addAction("Edit Book")
|
||||
action_edit_author = context_menu.addAction("Edit Author")
|
||||
action_mark_returned = context_menu.addAction("Mark as Returned")
|
||||
action_remove_reservation = context_menu.addAction("Remove reservation")
|
||||
context_menu.addSeparator()
|
||||
delete_book_action = context_menu.addAction("Delete Book")
|
||||
|
||||
if self.book_overview.status == BookStatusEnum.borrowed:
|
||||
action_mark_returned = context_menu.addAction("Mark as Returned")
|
||||
if self.book_overview.status != BookStatusEnum.borrowed:
|
||||
action_mark_returned.setVisible(False)
|
||||
|
||||
if self.book_overview.status == BookStatusEnum.reserved:
|
||||
action_remove_reservation = context_menu.addAction(
|
||||
"Remove reservation")
|
||||
if self.book_overview.status != BookStatusEnum.reserved:
|
||||
action_remove_reservation.setVisible(False)
|
||||
|
||||
action = context_menu.exec_(self.mapToGlobal(event.pos()))
|
||||
|
||||
@ -120,3 +123,5 @@ class BookCard(QWidget):
|
||||
print("Mark as Returned selected")
|
||||
elif action == action_remove_reservation:
|
||||
print("Remove reservation selected")
|
||||
elif action == delete_book_action:
|
||||
print("Delete book")
|
||||
|
34
src/ui/import_preview.py
Normal file
34
src/ui/import_preview.py
Normal file
@ -0,0 +1,34 @@
|
||||
from PySide6.QtWidgets import QDialog, QVBoxLayout, QTableWidget, QTableWidgetItem, QPushButton, QHeaderView
|
||||
|
||||
class PreviewDialog(QDialog):
|
||||
def __init__(self, books, parent=None):
|
||||
super().__init__(parent)
|
||||
|
||||
self.setWindowTitle("Preview Books to Import")
|
||||
self.setLayout(QVBoxLayout())
|
||||
self.setMinimumWidth(500)
|
||||
|
||||
# Table to display books
|
||||
table = QTableWidget(self)
|
||||
table.setRowCount(len(books))
|
||||
table.setColumnCount(4)
|
||||
table.setHorizontalHeaderLabels(["Title", "Author", "Year", "ISBN"])
|
||||
|
||||
for row, book in enumerate(books):
|
||||
table.setItem(row, 0, QTableWidgetItem(book.title))
|
||||
table.setItem(row, 1, QTableWidgetItem(f"{book.author.first_name} {book.author.last_name}"))
|
||||
table.setItem(row, 2, QTableWidgetItem(book.year_published))
|
||||
table.setItem(row, 3, QTableWidgetItem(book.isbn))
|
||||
|
||||
header = table.horizontalHeader()
|
||||
header.setSectionResizeMode(QHeaderView.Stretch)
|
||||
|
||||
self.layout().addWidget(table)
|
||||
|
||||
# Add buttons
|
||||
self.confirm_button = QPushButton("Confirm", self)
|
||||
self.cancel_button = QPushButton("Cancel", self)
|
||||
self.confirm_button.clicked.connect(self.accept)
|
||||
self.cancel_button.clicked.connect(self.reject)
|
||||
self.layout().addWidget(self.confirm_button)
|
||||
self.layout().addWidget(self.cancel_button)
|
@ -1,7 +1,8 @@
|
||||
from PySide6.QtGui import QGuiApplication, QAction, QIcon
|
||||
from PySide6.QtQml import QQmlApplicationEngine
|
||||
from PySide6 import QtWidgets, QtCore
|
||||
from PySide6.QtWidgets import QMessageBox
|
||||
from PySide6.QtWidgets import QMessageBox, QFileDialog
|
||||
from PySide6.QtCore import QStandardPaths
|
||||
|
||||
from ui.dashboard.dashboard import LibraryDashboard
|
||||
from ui.book_editor.book_editor import BookEditor
|
||||
@ -9,9 +10,14 @@ from ui.member_editor.member_editor import MemberEditor
|
||||
|
||||
from ui.settings import SettingsDialog
|
||||
|
||||
from ui.import_preview import PreviewDialog
|
||||
|
||||
from export.book_exporter import BookExporter
|
||||
from importer.book.book_importer import BookImporter
|
||||
|
||||
from utils.errors.export_error import ExportError
|
||||
from utils.errors.import_error.import_error import ImportError
|
||||
|
||||
|
||||
class LibraryWindow(QtWidgets.QMainWindow):
|
||||
def __init__(self):
|
||||
@ -19,6 +25,7 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
|
||||
# Set up main window properties
|
||||
self.setWindowTitle("Library App")
|
||||
self.setWindowIcon(QIcon.fromTheme("x-content-ebook-reader"))
|
||||
self.setGeometry(0, 0, 800, 600)
|
||||
|
||||
self.center_window()
|
||||
@ -31,9 +38,11 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
self.setCentralWidget(central_widget)
|
||||
|
||||
central_widget.addTab(LibraryDashboard(), "Dashboard")
|
||||
# central_widget.addTab(BookEditor(), "Books")
|
||||
central_widget.addTab(MemberEditor(), "Members")
|
||||
|
||||
|
||||
self.file_types = {
|
||||
"XML files (*.xml)": ".xml",
|
||||
"Any file type (*)": ""}
|
||||
|
||||
def center_window(self):
|
||||
# Get the screen geometry
|
||||
@ -52,8 +61,6 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
# Move the window to the calculated geometry
|
||||
self.move(window_geometry.topLeft())
|
||||
|
||||
|
||||
|
||||
def create_menu_bar(self):
|
||||
# Create the menu bar
|
||||
menu_bar = self.menuBar()
|
||||
@ -82,7 +89,7 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
file_menu.addMenu(import_submenu)
|
||||
|
||||
import_books_action = QAction("Import books", self)
|
||||
import_books_action.triggered.connect(self.import_data)
|
||||
import_books_action.triggered.connect(self.import_books)
|
||||
import_submenu.addAction(import_books_action)
|
||||
|
||||
import_members_action = QAction("Import members", self)
|
||||
@ -141,16 +148,62 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
|
||||
def export_books(self):
|
||||
try:
|
||||
file_path = QtWidgets.QFileDialog.getSaveFileName(self, "Save book export", "", ".xml;;")
|
||||
home_dir = QStandardPaths.writableLocation(
|
||||
QStandardPaths.HomeLocation)
|
||||
file_path, selected_filter = QFileDialog.getSaveFileName(self,
|
||||
"Save book export",
|
||||
home_dir,
|
||||
";;".join(self.file_types.keys()))
|
||||
if file_path:
|
||||
selected_filetype = self.file_types[selected_filter]
|
||||
|
||||
if file_path.endswith(selected_filetype):
|
||||
selected_filetype = ""
|
||||
|
||||
if file_path[0]:
|
||||
book_exporter = BookExporter()
|
||||
book_exporter.save_xml(file_path[0] + file_path[1])
|
||||
book_exporter.save_xml(file_path + selected_filetype)
|
||||
|
||||
except OSError as e:
|
||||
QMessageBox.critical(self, "Error saving file", f"An error occured when saving the exported data: {e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
|
||||
QMessageBox.critical(self, "Error saving file", f"Error occurred when saving the exported data: {
|
||||
e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
|
||||
except ExportError as e:
|
||||
QMessageBox.critical(self, "Error exporting books", f"An error occured when exporting books: {e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
|
||||
QMessageBox.critical(self, "Error exporting books", f"An error occurred when exporting books: {
|
||||
e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
|
||||
|
||||
def import_books(self):
|
||||
try:
|
||||
home_dir = QStandardPaths.writableLocation(
|
||||
QStandardPaths.HomeLocation)
|
||||
file_path, _ = QFileDialog.getOpenFileName(
|
||||
self, "Choose import file", home_dir, ";;".join(
|
||||
self.file_types.keys())
|
||||
)
|
||||
|
||||
if not file_path:
|
||||
return # User canceled
|
||||
|
||||
importer = BookImporter()
|
||||
books = importer.parse_xml(file_path)
|
||||
new_books = importer.filter_new_books(books)
|
||||
|
||||
if not new_books:
|
||||
QMessageBox.information(
|
||||
self, "No New Books", "No new books to import.", QMessageBox.Ok)
|
||||
return
|
||||
|
||||
# Show preview dialog
|
||||
dialog = PreviewDialog(new_books, self)
|
||||
if dialog.exec() == QtWidgets.QDialog.Accepted:
|
||||
# User confirmed, proceed with importing
|
||||
importer.save_books(new_books)
|
||||
QMessageBox.information(
|
||||
self, "Success", "Books imported successfully!", QMessageBox.Ok)
|
||||
else:
|
||||
QMessageBox.information(
|
||||
self, "Canceled", "Import was canceled.", QMessageBox.Ok)
|
||||
except ImportError as e:
|
||||
QMessageBox.critical(self, "Error importing books", f"An error occurred when importing books from the file provided: {
|
||||
e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
|
||||
|
||||
def new_book(self):
|
||||
pass
|
||||
@ -165,6 +218,7 @@ class LibraryWindow(QtWidgets.QMainWindow):
|
||||
pass
|
||||
|
||||
def about(self):
|
||||
QtWidgets.QMessageBox.information(self, "About", "Library app demonstrating the phantom read problem")
|
||||
QtWidgets.QMessageBox.information(
|
||||
self, "About", "Library app demonstrating the phantom read problem")
|
||||
|
||||
# endregion
|
||||
# endregion
|
||||
|
@ -1,7 +1,7 @@
|
||||
import enum
|
||||
import os
|
||||
|
||||
from utils.errors.database_config_error import DatabaseConfigError
|
||||
from utils.errors.database.database_config_error import DatabaseConfigError
|
||||
from dotenv import load_dotenv
|
||||
import logging
|
||||
|
||||
|
@ -1,10 +1,12 @@
|
||||
import logging
|
||||
|
||||
from sqlalchemy.orm import sessionmaker, Session
|
||||
from sqlalchemy import create_engine
|
||||
from sqlalchemy import create_engine, text
|
||||
|
||||
from sqlalchemy.exc import DatabaseError
|
||||
|
||||
from utils.config import DatabaseConfig
|
||||
from utils.errors.database_config_error import DatabaseConfigError
|
||||
from utils.errors.database.database_connection_error import DatabaseConnectionError
|
||||
|
||||
|
||||
class DatabaseManager():
|
||||
@ -19,15 +21,32 @@ class DatabaseManager():
|
||||
def __init__(self) -> None:
|
||||
self.logger = logging.getLogger(__name__)
|
||||
self.logger.info("Reading database config")
|
||||
self.database_config = DatabaseConfig()
|
||||
self.engine = create_engine(f'mysql+mysqlconnector://{self.database_config.user}:{
|
||||
self.database_config.password}@{self.database_config.host}/{self.database_config.name}')
|
||||
self.session_local = sessionmaker(bind=self.engine)
|
||||
database_config = DatabaseConfig()
|
||||
self.engine = create_engine('mysql+mysqlconnector://%s:%s@%s:%s/%s' % (
|
||||
database_config.user,
|
||||
database_config.password,
|
||||
database_config.host,
|
||||
database_config.port,
|
||||
database_config.name),
|
||||
pool_pre_ping=True)
|
||||
if self.test_connection():
|
||||
self.session_local = sessionmaker(bind=self.engine)
|
||||
|
||||
def cleanup(self) -> None:
|
||||
self.logger.debug("Closing connection")
|
||||
self.engine.dispose()
|
||||
|
||||
def test_connection(self) -> bool:
|
||||
self.logger.debug("Testing database connection")
|
||||
try:
|
||||
with self.engine.connect() as connection:
|
||||
connection.execute(text("select 1"))
|
||||
self.logger.debug("Database connection successful")
|
||||
return True
|
||||
except DatabaseError as e:
|
||||
self.logger.error(f"Database connection failed: {e}")
|
||||
raise DatabaseConnectionError("Database connection failed") from e
|
||||
|
||||
@classmethod
|
||||
def get_session(cls) -> Session:
|
||||
return DatabaseManager._instance.session_local()
|
||||
|
0
src/utils/errors/database/__init__.py
Normal file
0
src/utils/errors/database/__init__.py
Normal file
5
src/utils/errors/database/database_connection_error.py
Normal file
5
src/utils/errors/database/database_connection_error.py
Normal file
@ -0,0 +1,5 @@
|
||||
from .database_error import DatabaseError
|
||||
class DatabaseConnectionError(DatabaseError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
self.message = message
|
4
src/utils/errors/database/database_error.py
Normal file
4
src/utils/errors/database/database_error.py
Normal file
@ -0,0 +1,4 @@
|
||||
class DatabaseError(Exception):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
self.message = message
|
0
src/utils/errors/import_error/__init__.py
Normal file
0
src/utils/errors/import_error/__init__.py
Normal file
5
src/utils/errors/import_error/import_error.py
Normal file
5
src/utils/errors/import_error/import_error.py
Normal file
@ -0,0 +1,5 @@
|
||||
class ImportError(Exception):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
self.message = message
|
8
src/utils/errors/import_error/invalid_contents_error.py
Normal file
8
src/utils/errors/import_error/invalid_contents_error.py
Normal file
@ -0,0 +1,8 @@
|
||||
from .import_error import ImportError
|
||||
|
||||
|
||||
class InvalidContentsError(ImportError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
self.message = message
|
8
src/utils/errors/import_error/xsd_scheme_not_found.py
Normal file
8
src/utils/errors/import_error/xsd_scheme_not_found.py
Normal file
@ -0,0 +1,8 @@
|
||||
from .import_error import ImportError
|
||||
|
||||
|
||||
class XsdSchemeNotFoundError(ImportError):
|
||||
def __init__(self, message: str):
|
||||
super().__init__(message)
|
||||
|
||||
self.message = message
|
Loading…
x
Reference in New Issue
Block a user