Compare commits

...

2 Commits

33 changed files with 374 additions and 112 deletions

Binary file not shown.

View File

@ -1,11 +1,13 @@
from .manager import *
from .book import *
from .book_category_statistics import *
from .member import *
from .book_overview import *
__all__ = [
*manager.__all__,
*book.__all__,
*book_category_statistics.__all__,
*book_overview.__all__,
*member.__all__,
]

View File

@ -1,14 +1,19 @@
from typing import Dict, List, Optional
import logging
import time
from sqlalchemy.exc import IntegrityError, SQLAlchemyError, DatabaseError as SqlAlchemyDatabaseError
from sqlalchemy.orm import joinedload
from sqlalchemy import delete
from utils.errors.database import DatabaseError, DuplicateEntryError, DatabaseConnectionError
from models import Book
from database.manager import DatabaseManager
from .author import get_or_create_author
from .book_category import get_or_create_categories
from .book_category_statistics import update_category_statistics
from utils.config import UserConfig
logger = logging.getLogger(__name__)
@ -83,9 +88,22 @@ def create_books(books: List[Dict[str, object]]) -> None:
categories=categories
)
session.add(new_book)
user_config = UserConfig()
session.commit()
logger.info(f"Book {book['title']} successfully created.")
if user_config.simulate_slowdown:
logger.debug("Simulating slowdown before updating statistics for 10 seconds")
time.sleep(10)
else:
logger.debug("Performing category statistics update normally")
update_category_statistics(session)
session.commit()
# logger.info(f"Book {book['title']} successfully created.")
logger.debug("Committing all changes")
session.commit()
except IntegrityError as e:
logger.warning("Data already exists")
raise DuplicateEntryError("Data already exists in the database") from e
@ -142,5 +160,17 @@ def update_book(book: Dict[str, object]) -> None:
logger.error(f"An error occurred when updating the book: {e}")
raise DatabaseError("An error occurred when updating the book") from e
def delete_book(book_id: int) -> None:
try:
with DatabaseManager.get_session() as session:
stmt = delete(Book).where(Book.id == book_id)
session.execute(stmt)
session.commit()
except SqlAlchemyDatabaseError as e:
logger.critical("Connection with database interrupted")
raise DatabaseConnectionError("Connection with database interrupted") from e
except SQLAlchemyError as e:
logger.error(f"An error occurred when updating the book: {e}")
raise DatabaseError("An error occurred when updating the book") from e
__all__ = ["create_book", "create_books", "update_book", "fetch_all_books"]

View File

@ -1,23 +1,42 @@
from sqlalchemy.orm import Session
from sqlalchemy import update
from sqlalchemy import func
from models import BookCategoryStatistics, Book
from models import BookCategoryStatistics, BookCategoryLink
def update_category_statistics(session: Session, book_id: int):
book = session.query(Book).filter_by(id=book_id).first()
def update_category_statistics(session: Session) -> None:
"""
Updates category statistics by calculating the count of books in each category.
if not book:
raise ValueError(f"Book with ID {book_id} does not exist.")
:param session: SQLAlchemy session object.
"""
# Calculate the book count for each category using a query
category_counts = (
session.query(
BookCategoryLink.book_category_id,
func.count(BookCategoryLink.book_id).label('book_count')
)
.group_by(BookCategoryLink.book_category_id)
.all()
)
for category in book.categories:
statistics = session.query(BookCategoryStatistics).filter_by(category_id=category.id).one_or_none()
# Update or create statistics based on the query results
for category_id, book_count in category_counts:
existing_statistics = (
session.query(BookCategoryStatistics)
.filter_by(book_category_id=category_id)
.one_or_none()
)
if statistics:
statistics.book_count += 1
session.add(statistics)
if existing_statistics:
# Update the existing count
existing_statistics.book_count = book_count
else:
new_statistics = BookCategoryStatistics(category_id=category.id, book_count=1)
# Create new statistics for the category
new_statistics = BookCategoryStatistics(
book_category_id=category_id,
book_count=book_count
)
session.add(new_statistics)

View File

@ -5,7 +5,7 @@ from sqlalchemy import create_engine, text
from sqlalchemy.exc import DatabaseError
from utils.config import DatabaseConfig
from utils.config import DatabaseConfig, UserConfig
from utils.errors.database import DatabaseConnectionError
@ -49,6 +49,14 @@ class DatabaseManager():
@classmethod
def get_session(cls) -> Session:
return DatabaseManager._instance.Session()
user_config = UserConfig()
# Get the transaction level as a string (e.g., "READ COMMITTED", "SERIALIZABLE")
isolation_level = user_config.transaction_level.value
# Create a session with the appropriate transaction isolation level
session = cls._instance.Session()
session.connection(execution_options={"isolation_level": isolation_level})
return session
__all__ = ["DatabaseManager"]

View File

@ -2,6 +2,7 @@ import logging
from typing import List, Dict
from sqlalchemy.exc import IntegrityError, SQLAlchemyError, DatabaseError as SqlAlchemyDatabaseError
from sqlalchemy import delete
from utils.errors.database import DatabaseError, DuplicateEntryError, DatabaseConnectionError
from models import Member
@ -33,12 +34,29 @@ def create_member(new_member: Dict[str, str]):
def create_members(members: List[Dict[str, str]]):
try:
with DatabaseManager.get_session() as session:
session.add_all(members)
for member_dict in members:
member = Member(
first_name=member_dict["first_name"],
last_name=member_dict["last_name"],
email=member_dict["email"],
phone=member_dict["phone_number"]
)
session.add(member)
session.commit()
except IntegrityError as e:
session.rollback()
logger.warning("Data already exists")
raise DuplicateEntryError("Data already exists in the database") from e
if "email" in str(e.orig):
logger.warning("Email is already in use")
raise DuplicateEntryError("Email", "Email is already in use") from e
elif "phone" in str(e.orig):
logger.warning("Phone number is already in use")
raise DuplicateEntryError("Phone number", "Phone number is already in use") from e
else:
logger.error("Member exists already in the database")
raise DatabaseError("Member exists already") from e
except DatabaseError as e:
session.rollback()
logger.critical("Connection with database interrupted")
@ -51,4 +69,17 @@ def create_members(members: List[Dict[str, str]]):
def update_member(member: Dict[str, str]):
pass
__all__ = ["create_member", "create_members", "fetch_all_members"]
def delete_member(member_id: int) -> None:
try:
with DatabaseManager.get_session() as session:
stmt = delete(Member).where(Member.id == member_id)
session.execute(stmt)
session.commit()
except SqlAlchemyDatabaseError as e:
logger.critical("Connection with database interrupted")
raise DatabaseConnectionError("Connection with database interrupted") from e
except SQLAlchemyError as e:
logger.error(f"An error occurred when deleting member: {e}")
raise DatabaseError("An error occurred when deleting member") from e
__all__ = ["create_member", "create_members", "fetch_all_members", "delete_member"]

View File

@ -1,21 +1,23 @@
from .author import *
from .book import *
from .book_category import *
from .book_category_link import *
from .book_category_statistics import *
from .book_overview import *
from .member import *
from .librarian import *
from .loan import *
from .author_model import *
from .book_model import *
from .book_category_model import *
from .book_category_link_model import *
from .book_category_statistics_model import *
from .book_category_statistics_overview_model import *
from .book_overview_model import *
from .member_model import *
from .librarian_model import *
from .loan_model import *
__all__ = [
*author.__all__,
*book.__all__,
*book_category.__all__,
*book_category_link.__all__,
*book_category_statistics.__all__,
*book_overview.__all__,
*member.__all__,
*librarian.__all__,
*loan.__all__
*author_model.__all__,
*book_model.__all__,
*book_category_model.__all__,
*book_category_link_model.__all__,
*book_category_statistics_model.__all__,
*book_category_statistics_overview_model.__all__,
*book_overview_model.__all__,
*member_model.__all__,
*librarian_model.__all__,
*loan_model.__all__
]

View File

@ -1,7 +1,7 @@
from sqlalchemy import Column, Integer, String, TIMESTAMP, UniqueConstraint, func
from sqlalchemy.orm import relationship
from .base import Base
from .base_model import Base
class Author(Base):

View File

@ -1,9 +1,9 @@
from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.orm import relationship
from .book import Book
from .book_category import BookCategory
from .base import Base
from .book_model import Book
from .book_category_model import BookCategory
from .base_model import Base
class BookCategoryLink(Base):

View File

@ -1,7 +1,7 @@
from sqlalchemy import Column, Integer, String, TIMESTAMP, ForeignKey, UniqueConstraint, func
from sqlalchemy.orm import relationship
from .base import Base
from .base_model import Base
class BookCategory(Base):
__tablename__ = 'book_category'

View File

@ -3,13 +3,13 @@ from sqlalchemy import Column, Integer, ForeignKey
from sqlalchemy.dialects.mysql import INTEGER
from sqlalchemy.orm import relationship
from .base import Base
from .base_model import Base
class BookCategoryStatistics(Base):
__tablename__ = 'book_category_statistics'
book_category_id = Column(Integer, ForeignKey('book_category.id', ondelete="cascade"), primary_key=True)
count = Column(INTEGER(unsigned=True), nullable=False, default=0)
book_count = Column(INTEGER(unsigned=True), nullable=False, default=0)
category = relationship(
'BookCategory',

View File

@ -0,0 +1,18 @@
from sqlalchemy import Column, String, TIMESTAMP, Integer, Text, Enum
from sqlalchemy.dialects.mysql import INTEGER
from .base_model import Base
class BookCategoryStatisticsOverview(Base):
__tablename__ = 'book_category_statistics_overview'
__table_args__ = {'extend_existing': True}
name = Column(String)
book_count = Column(INTEGER(unsigned=True), default=0)
def __repr__(self):
return (f"<BookCategoryStatisticsOverview(book_category_id={self.book_category_id}, book_count={self.book_count})>")
__all__ = ["BookCategoryStatisticsOverview"]

View File

@ -3,7 +3,7 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, func
from sqlalchemy.orm import relationship
from .base import Base
from .base_model import Base
class BookStatusEnum(enum.Enum):

View File

@ -1,8 +1,7 @@
from sqlalchemy import Column, String, TIMESTAMP, Integer, Text, Enum
from .base import Base
from models.book import BookStatusEnum
from .base_model import Base
from .book_model import BookStatusEnum
class BooksOverview(Base):
__tablename__ = 'books_overview'

View File

@ -3,7 +3,7 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, func
from sqlalchemy.orm import relationship
from .base import Base
from .base_model import Base
class LibrarianStatusEnum(enum.Enum):

View File

@ -3,10 +3,10 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, Float, func
from sqlalchemy.orm import relationship
from .base import Base
from .book import Book
from .member import Member
from .librarian import Librarian
from .base_model import Base
from .book_model import Book
from .member_model import Member
from .librarian_model import Librarian
class LoanStatusEnum(enum.Enum):

View File

@ -3,7 +3,7 @@ import enum
from sqlalchemy import Column, Integer, String, TIMESTAMP, Text, ForeignKey, Enum, UniqueConstraint, func
from sqlalchemy.orm import relationship
from .base import Base
from .base_model import Base
class MemberStatusEnum(enum.Enum):
@ -13,7 +13,7 @@ class MemberStatusEnum(enum.Enum):
class Member(Base):
__tablename__ = 'member'
__table_args__ = (UniqueConstraint('id'),)
__table_args__ = (UniqueConstraint('id'), UniqueConstraint('email'), UniqueConstraint('phone'))
id = Column(Integer, primary_key=True, autoincrement=True)
first_name = Column(String(50), nullable=False)

View File

@ -17,10 +17,8 @@ from models import Book
from database import fetch_all_books, create_books
from assets import asset_manager
# Initialize logger and XML Schema
logger = logging.getLogger(__name__)
try:
logger.debug("Loading XSD schema")
SCHEMA = XMLSchema(asset_manager.get_asset("book_import_scheme.xsd"))

View File

@ -126,7 +126,7 @@ class BookEditor(QDialog):
"Could not connect to the database",
QMessageBox.StandardButton.Ok)
except DatabaseError as e:
QMessageBox.critical(self.parent,
QMessageBox.critical(None,
"An error occurred",
f"Could not save the book because of the following error: {e}",
QMessageBox.StandardButton.Ok)

View File

@ -9,7 +9,7 @@ from PySide6.QtWidgets import QVBoxLayout, QFormLayout, QLineEdit, QHBoxLayout,
from models import Member
from database.member import create_new_member, update_member
from database.member import create_member, update_member
from utils.errors.database import DatabaseError, DatabaseConnectionError, DuplicateEntryError
@ -86,40 +86,41 @@ class MemberEditor(QDialog):
QMessageBox.information(None,
"Success",
"Member created successfully",
QMessageBox.StandardButton.Ok)
QMessageBox.StandardButton.Ok,
QMessageBox.StandardButton.NoButton)
else:
member_object["id"] = self.member_id
update_member(book_object)
QMessageBox.information(None,
"Success",
"Member updated successfully",
QMessageBox.StandardButton.Ok)
QMessageBox.StandardButton.Ok,
QMessageBox.StandardButton.NoButton)
self.accept()
except ValueError as e:
QMessageBox.critical(None,
"Invalid Input",
f"Input validation failed: {e}",
QMessageBox.StandardButton.Ok)
QMessageBox.StandardButton.Ok,
QMessageBox.StandardButton.NoButton)
except DuplicateEntryError as e:
QMessageBox.critical(None,
"ISBN is already in use",
"The ISBN provided is already in use",
QMessageBox.StandardButton.Ok)
f"Duplicate {e.duplicate_entry_name}",
f"The {e.duplicate_entry_name} is already in use",
QMessageBox.StandardButton.Ok,
QMessageBox.StandardButton.NoButton)
except DatabaseConnectionError as e:
QMessageBox.critical(None,
"Failed to save",
"Connection error",
"Could not connect to the database",
QMessageBox.StandardButton.Ok)
except DatabaseError as e:
QMessageBox.critical(self.parent,
"An error occurred",
QMessageBox.critical(None,
"Unknown database error",
f"Could not save the book because of the following error: {e}",
QMessageBox.StandardButton.Ok)
self.accept()
def parse_inputs(self) -> Dict:
first_name = self.first_name_input.text().strip()
if not first_name or len(first_name) > 50:
@ -146,4 +147,5 @@ class MemberEditor(QDialog):
"phone_number": phone_number
}
__all__ = ["MemberEditor"]

View File

@ -91,8 +91,7 @@ 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")
action_remove_reservation = context_menu.addAction("Remove reservation")
context_menu.addSeparator()
delete_book_action = context_menu.addAction("Delete Book")
delete_book_action.triggered.connect(self.delete_book)

View File

@ -15,8 +15,9 @@ from ui.editor import MemberEditor
class BookOverviewList(QWidget):
def __init__(self):
super().__init__()
def __init__(self, parent = None):
self.parent = parent
super().__init__(parent=parent)
# Central widget and layout
main_layout = QVBoxLayout(self)
@ -78,6 +79,7 @@ class BookOverviewList(QWidget):
def register_member(self):
MemberEditor().exec()
self.parent.refresh_member_cards()
def add_borrow_record(self):
QMessageBox.information(self, "Add Borrow Record",

View File

@ -0,0 +1,58 @@
from PySide6.QtGui import QGuiApplication, QAction, Qt
from PySide6.QtQml import QQmlApplicationEngine
from PySide6.QtWidgets import QHBoxLayout, QVBoxLayout, QLabel, QWidget, QMenu, QSizePolicy, QLayout, QMessageBox
from PySide6.QtCore import qDebug
from ui.editor import BookEditor
from models import BookCategoryStatisticsOverview
from database.manager import DatabaseManager
class BookCategoryStatisticsOverviewCard(QWidget):
def __init__(self, book_category_statistics_overview: BookCategoryStatisticsOverview):
super().__init__()
self.book_category_statistics_overview = book_category_statistics_overview
self.setAttribute(Qt.WidgetAttribute.WA_Hover, True) # Enable hover events
# Enable styling for background
self.setAttribute(Qt.WidgetAttribute.WA_StyledBackground, True)
# Set initial stylesheet with hover behavior
self.setStyleSheet("""
BookCategoryStatisticsOverviewCard:hover {
background-color: palette(highlight);
}
""")
# Layout setup
layout = QHBoxLayout(self)
layout.setSizeConstraint(QLayout.SizeConstraint.SetMinimumSize)
self.setSizePolicy(QSizePolicy.Policy.Preferred,
QSizePolicy.Policy.Fixed)
layout.setContentsMargins(10, 10, 10, 10)
layout.setSpacing(10)
# Left-side content
left_side = QVBoxLayout()
layout.addLayout(left_side)
category_name_label = QLabel(book_category_statistics_overview.name)
category_name_label.setStyleSheet("font-size: 20px; font-weight: bold;")
left_side.addWidget(category_name_label)
# Right-side content
right_side = QVBoxLayout()
layout.addLayout(right_side)
status_label = QLabel(book_category_statistics_overview.book_count)
status_label.setStyleSheet(f"font-size: 20px; font-weight: bold;")
status_label.setAlignment(Qt.AlignmentFlag.AlignRight)
right_side.addWidget(status_label)
self.setLayout(layout)
__all__ = ["BookCategoryStatisticsOverviewCard"]

View File

@ -0,0 +1,96 @@
from PySide6.QtGui import QAction
from PySide6.QtWidgets import (
QWidget, QVBoxLayout, QHBoxLayout, QLabel, QLineEdit, QScrollArea,
QFrame, QPushButton, QMessageBox, QVBoxLayout
)
from PySide6.QtCore import Qt
from .book_card import BookCard
from models import BooksOverview
from database.manager import DatabaseManager
from database.book_overview import fetch_all_book_overviews
from ui.editor import MemberEditor
class BookCategoryStatisticsOverview(QWidget):
def __init__(self, parent = None):
self.parent = parent
super().__init__(parent=parent)
# Central widget and layout
main_layout = QVBoxLayout(self)
# Title label
title_label = QLabel("Category statistics", self)
title_label.setAlignment(Qt.AlignCenter)
title_label.setStyleSheet(
"font-size: 20px; font-weight: bold; color: #0078D4;")
main_layout.addWidget(title_label)
# Search bar
self.search_input = QLineEdit()
self.search_input.setPlaceholderText("Search in categories...")
self.search_input.textChanged.connect(self.filter_categories)
main_layout.addWidget(self.search_input)
# Scrollable area for cards
self.scroll_area = QScrollArea()
self.scroll_area.setWidgetResizable(True)
# Container widget for the scroll area
self.scroll_widget = QWidget()
self.scroll_layout = QVBoxLayout(self.scroll_widget)
self.scroll_layout.setSpacing(5) # Set gap between individual cards
self.scroll_layout.setContentsMargins(0, 0, 0, 0) # Remove spacing from all sides which is present by default
# Align the cards to the top
self.scroll_layout.setAlignment(Qt.AlignTop)
self.books = []
self.book_cards = []
self.redraw_cards()
self.scroll_widget.setLayout(self.scroll_layout)
self.scroll_area.setWidget(self.scroll_widget)
main_layout.addWidget(self.scroll_area)
main_layout.addLayout(button_layout)
def filter_categories(self, text):
"""Filter the cards based on the search input."""
for card, book in zip(self.book_cards, self.books):
title_contains_text = text.lower() in book.title.lower()
author_name_contains_text = text.lower() in book.author_name.lower()
isbn_contains_text = text.lower() in book.isbn
card.setVisible(title_contains_text or author_name_contains_text or isbn_contains_text)
def clear_layout(self, layout):
while layout.count():
item = layout.takeAt(0)
widget = item.widget()
if widget is not None:
widget.deleteLater()
else:
sub_layout = item.layout()
if sub_layout is not None:
self.clear_layout(sub_layout)
del item
def redraw_cards(self):
self.clear_layout(self.scroll_layout)
self.book_cards = []
self.books = fetch_all_book_overviews()
for book in self.books:
card = BookCard(book)
self.scroll_layout.addWidget(card)
self.book_cards.append(card)
__all__ = ["BookCategoryStatisticsOverview"]

View File

@ -6,8 +6,11 @@ from PySide6.QtCore import qDebug
from models import Member, MemberStatusEnum
from database.manager import DatabaseManager
from database import delete_member
from sqlalchemy import delete
from utils.errors import DatabaseConnectionError, DatabaseError
STATUS_TO_COLOR_MAP = {
MemberStatusEnum.active: "#3c702e",
MemberStatusEnum.inactive: "#702525"
@ -96,19 +99,17 @@ class MemberCard(QWidget):
self.update_member_status(MemberStatusEnum.active)
def delete_member(self):
self.make_sure()
# if not self.make_sure():
# return
# with DatabaseManager.get_session() as session:
# try:
# stmt = delete(Member).where(Member.id == self.member.id)
# session.execute(stmt)
# session.commit()
# self.setVisible(False)
# except Exception as e:
# session.rollback()
# print(e)
if not self.make_sure():
return
try:
delete_member(self.member.id)
self.setVisible(False)
except DatabaseConnectionError as e:
QMessageBox.critical(None, "Failed", "Connection with database failed", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
except DatabaseError as e:
QMessageBox.critical(None, "Failed", f"An error occured when deleting member: {e}", QMessageBox.StandardButton.Ok, QMessageBox.StandardButton.NoButton)
def update_member_status(self, new_status):
with DatabaseManager.get_session() as session:

View File

@ -57,27 +57,19 @@ class MemberList(QWidget):
register_member_button.clicked.connect(self.register_member)
button_layout.addWidget(register_member_button)
delete_member_button = QPushButton("Delete Member")
delete_member_button.clicked.connect(self.delete_member)
button_layout.addWidget(delete_member_button)
main_layout.addLayout(button_layout)
def filter_members(self, text):
"""Filter the cards based on the search input."""
for card, member in zip(self.member_cards, self.members):
name_contains_text = text.lower() in member.name.lower()
id_contains_text = text.lower() in str(member.id)
first_name_contains_text = text.lower() in member.first_name.lower()
last_name_contains_text = text.lower() in member.last_name.lower()
card.setVisible(name_contains_text or id_contains_text)
card.setVisible(first_name_contains_text or last_name_contains_text)
def register_member(self):
MemberEditor().exec()
def delete_member(self):
QMessageBox.information(self, "Delete Member",
"Open dialog to delete a member.")
def clear_layout(self, layout):
while layout.count():
item = layout.takeAt(0)

View File

@ -6,7 +6,7 @@ from ui.settings import SettingsDialog
from ui.import_preview import PreviewDialog
from ui.editor import BookEditor, MemberEditor
from utils.errors import ExportError, ExportFileError
from utils.errors import ExportError, ExportFileError, InvalidContentsError
from services import book_service, book_overview_service
@ -107,10 +107,11 @@ class MenuBar(QMenuBar):
self.parent.refresh_member_cards()
def import_books(self):
self.import_data("Book", book_service)
self.import_data("Book", None, book_service)
def import_members(self):
self.import_data("Member", memb)
# self.import_data("Member", memb)
pass
def export_books(self):
self.export_data("Book", book_service)
@ -147,6 +148,11 @@ class MenuBar(QMenuBar):
self.parent.refresh_book_cards()
else:
QMessageBox.information(self, "Canceled", "Import was canceled.", QMessageBox.Ok)
except InvalidContentsError as e:
QMessageBox.critical(self,
"Invalid file",
"The file you selected is invalid",
QMessageBox.StandardButton.Ok)
except ImportError as e:
QMessageBox.critical(self,
"Error importing books",

View File

@ -22,7 +22,7 @@ class LibraryWindow(QMainWindow):
central_widget = QTabWidget()
self.setCentralWidget(central_widget)
self.dashboard = BookOverviewList()
self.dashboard = BookOverviewList(self)
self.member_list = MemberList()
central_widget.addTab(self.dashboard, "Dashboard")
central_widget.addTab(self.member_list, "Members")

View File

@ -82,6 +82,8 @@ class UserConfig:
self._transaction_level = TransactionLevel.insecure
if not hasattr(self, "_simulate_slowdown"):
self._simulate_slowdown = False
if not hasattr(self, "logger"):
self.logger = logging.getLogger(__name__)
@property
def transaction_level(self) -> TransactionLevel:
@ -93,6 +95,7 @@ class UserConfig:
raise TypeError(
f"Invalid value for 'transaction_level'. Must be a TransactionLevel enum, got {type(value).__name__}."
)
self.logger.debug(f"Transaction isolation level set to: {value}")
self._transaction_level = value
@property
@ -105,14 +108,9 @@ class UserConfig:
raise TypeError(
f"Invalid value for 'simulate_slowdown'. Must be a boolean, got {type(value).__name__}."
)
self.logger.debug(f"Slowdown simulation set to: {value}")
self._simulate_slowdown = value
@classmethod
def get_friendly_name(cls, option: str) -> str:
return cls._metadata.get(option, {}).get("friendly_name", option)
def __dict__(self) -> dict:
return {
"transaction_level": self.transaction_level,
"simulate_slowdown": self.simulate_slowdown,
}

View File

@ -20,8 +20,9 @@ class DatabaseConnectionError(DatabaseError):
class DuplicateEntryError(DatabaseError):
def __init__(self, message: str):
def __init__(self, duplicate_entry_name: str, message: str):
super().__init__(message)
self.duplicate_entry_name = duplicate_entry_name
self.message = message