feat(log): add logger

This commit is contained in:
Kristofers Solo 2023-11-08 15:03:29 +02:00
parent 2ed46444bb
commit dbb16c9e9a
6 changed files with 44 additions and 7 deletions

View File

@ -6,7 +6,12 @@ authors = [{ name = "Kristofers Solo", email = "dev@kristofers.xyz" }]
readme = "README.md" readme = "README.md"
requires-python = ">=3.11" requires-python = ">=3.11"
license = { text = "MIT" } license = { text = "MIT" }
dependencies = ["attrs==23.1.0", "customtkinter==5.2.1", "packaging==23.2"] dependencies = [
"attrs==23.1.0",
"customtkinter==5.2.1",
"loguru==0.7.2",
"packaging==23.2",
]
[tool.mypy] [tool.mypy]
check_untyped_defs = true check_untyped_defs = true

View File

@ -1,3 +1,4 @@
attrs>=23.1.0 attrs>=23.1.0
customtkinter>=5.2.1 customtkinter>=5.2.1
loguru>=0.7.2
packaging>=23.2 packaging>=23.2

View File

@ -1,18 +1,22 @@
from attrs import define, field, setters, validators from attrs import define, field, setters, validators
from loguru import logger
from .isbn import ISBN from .isbn import ISBN
@logger.catch
def _check_price_value(instance, attribute, value): def _check_price_value(instance, attribute, value):
if value < 0: if value < 0:
raise ValueError("Price must be larger or equal to 0!") raise ValueError("Price must be larger or equal to 0!")
@logger.catch
def _check_stock_value(instance, attribute, value): def _check_stock_value(instance, attribute, value):
if value < 0: if value < 0:
raise ValueError("Stock must be larger or equal to 0!") raise ValueError("Stock must be larger or equal to 0!")
@logger.catch
def _to_isbn(number: str): def _to_isbn(number: str):
return ISBN(number) return ISBN(number)
@ -26,12 +30,15 @@ class Book:
stock: int = field(converter=int, validator=[validators.instance_of(int), _check_stock_value]) stock: int = field(converter=int, validator=[validators.instance_of(int), _check_stock_value])
@classmethod @classmethod
@logger.catch
def fields(cls) -> tuple[str, str, str, str, str]: def fields(cls) -> tuple[str, str, str, str, str]:
return "ISBN", "Title", "Author", "Price", "Stock" return "ISBN", "Title", "Author", "Price", "Stock"
@logger.catch
def values(self) -> tuple[ISBN, str, str, float, int]: def values(self) -> tuple[ISBN, str, str, float, int]:
return self.isbn, self.title, self.author, self.price, self.stock return self.isbn, self.title, self.author, self.price, self.stock
@logger.catch
def get(self, field: str, default: str = "") -> ISBN | str | float | int: def get(self, field: str, default: str = "") -> ISBN | str | float | int:
match field: match field:
case "ISBN": case "ISBN":
@ -47,5 +54,6 @@ class Book:
case _: case _:
return default return default
@logger.catch
def __iter__(self): def __iter__(self):
yield from self.values() yield from self.values()

View File

@ -1,11 +1,14 @@
import sqlite3 import sqlite3
from pathlib import Path from pathlib import Path
from loguru import logger
from .book import Book from .book import Book
from .isbn import ISBN from .isbn import ISBN
class Inventory: class Inventory:
@logger.catch
def __init__(self, db_path: Path) -> None: def __init__(self, db_path: Path) -> None:
self.conn = sqlite3.connect(db_path) self.conn = sqlite3.connect(db_path)
self.cursor = self.conn.cursor() self.cursor = self.conn.cursor()
@ -21,24 +24,28 @@ class Inventory:
""" """
) )
@logger.catch
def save(self) -> None: def save(self) -> None:
"""Save `Inventory` to SQLite database.""" """Save `Inventory` to SQLite database."""
self.conn.commit() self.conn.commit()
@logger.catch
def close(self) -> None: def close(self) -> None:
"""Close database connection.""" """Close database connection."""
self.conn.close() self.conn.close()
@logger.catch
def add(self, *books: Book) -> None: def add(self, *books: Book) -> None:
"""Add `Book` to the `Inventory`. `Book`s ISBN must be unique.""" """Add `Book` to the `Inventory`. `Book`s ISBN must be unique."""
for book in books: for book in books:
try: try:
self.cursor.execute("INSERT INTO Book VALUES (?, ?, ?, ?, ?)", (book.isbn, book.title, book.author, book.price, book.stock)) self.cursor.execute("INSERT INTO Book VALUES (?, ?, ?, ?, ?)", (book.isbn, book.title, book.author, book.price, book.stock))
self.save() self.save()
print(f"Book with ISBN: {book.isbn} was successfully saved") logger.info(f"Create: {book}")
except sqlite3.InternalError as e: except sqlite3.InternalError as e:
print(f"A book with ISBN {book.isbn} already exists in the database.\n{e}") logger.error(f"A book with ISBN {book.isbn} already exists in the database.\t{e}")
@logger.catch
def edit(self, book: Book) -> None: def edit(self, book: Book) -> None:
"""Edit `Book`.""" """Edit `Book`."""
try: try:
@ -46,20 +53,22 @@ class Inventory:
"UPDATE Book SET title = ?, author = ?, price = ?, stock = ? WHERE isbn = ?", (book.title, book.author, book.price, book.stock, book.isbn) "UPDATE Book SET title = ?, author = ?, price = ?, stock = ? WHERE isbn = ?", (book.title, book.author, book.price, book.stock, book.isbn)
) )
self.save() self.save()
print(f"Book with ISBN: {book.isbn} was successfully updated!") logger.info(f"Update: {book}")
except sqlite3.InternalError as e: except sqlite3.InternalError as e:
print(f"Something went wrong.\n{e}") logger.error(f"Something went wrong.\t{e}")
@logger.catch
def delete(self, isbn: ISBN) -> Book | None: def delete(self, isbn: ISBN) -> Book | None:
"""Deletes `Book` from `Inventory` by `ISBN` and returns deleted `Book`""" """Deletes `Book` from `Inventory` by `ISBN` and returns deleted `Book`"""
deleted_book = self.find_by_isbn(isbn) deleted_book = self.find_by_isbn(isbn)
self.cursor.execute("DELETE FROM Book WHERE isbn = ?", (isbn,)) self.cursor.execute("DELETE FROM Book WHERE isbn = ?", (isbn,))
self.save() self.save()
print(f"Book with ISBN: {isbn} was successfully deleted!") logger.info(f"Delete: {deleted_book}")
return deleted_book return deleted_book
@logger.catch
def find_by_isbn(self, isbn: ISBN) -> Book | None: def find_by_isbn(self, isbn: ISBN) -> Book | None:
"""Looks up `Book` within `Inventory` by book `ISBN` and returns it. Returns `None` if it doesn't exist.""" """Looks up `Book` within `Inventory` by book `ISBN` and returns it. Returns `None` if it doesn't exist."""
self.cursor.execute("SELECT * FROM Book WHERE isbn = ?", (isbn,)) self.cursor.execute("SELECT * FROM Book WHERE isbn = ?", (isbn,))
@ -68,6 +77,7 @@ class Inventory:
return None return None
return Book(*book) return Book(*book)
@logger.catch
def find_by_title(self, title: str) -> list[Book] | None: def find_by_title(self, title: str) -> list[Book] | None:
"""Looks up `Book`s within `Inventory` by book title and returns them as a `List`. Returns `None` if none were found""" """Looks up `Book`s within `Inventory` by book title and returns them as a `List`. Returns `None` if none were found"""
self.cursor.execute("SELECT * FROM Book WHERE title LIKE ?", (f"%{title}%",)) self.cursor.execute("SELECT * FROM Book WHERE title LIKE ?", (f"%{title}%",))
@ -76,6 +86,7 @@ class Inventory:
return None return None
return [Book(*book) for book in books] return [Book(*book) for book in books]
@logger.catch
def find_by_author(self, author: str) -> list[Book] | None: def find_by_author(self, author: str) -> list[Book] | None:
"""Looks up `Book`s within `Inventory` by book author and returns them as a `List`. Returns `None` if none were found""" """Looks up `Book`s within `Inventory` by book author and returns them as a `List`. Returns `None` if none were found"""
self.cursor.execute("SELECT * FROM Book WHERE author LIKE ?", (f"%{author}%",)) self.cursor.execute("SELECT * FROM Book WHERE author LIKE ?", (f"%{author}%",))
@ -84,6 +95,7 @@ class Inventory:
return None return None
return [Book(*book) for book in books] return [Book(*book) for book in books]
@logger.catch
def list_all(self) -> list[Book | None]: def list_all(self) -> list[Book | None]:
"""Returns `List` of all `Book`s.""" """Returns `List` of all `Book`s."""
self.cursor.execute("SELECT * FROM Book") self.cursor.execute("SELECT * FROM Book")

View File

@ -1,7 +1,6 @@
from attrs import define, field, validators from attrs import define, field, validators
# TODO: create checksum method
@define(frozen=True) @define(frozen=True)
class ISBN(str): class ISBN(str):
number: str = field(converter=str, validator=validators.matches_re(r"^\d{10}$|^\d{13}$")) number: str = field(converter=str, validator=validators.matches_re(r"^\d{10}$|^\d{13}$"))

View File

@ -4,9 +4,21 @@
from pathlib import Path from pathlib import Path
from bookstore.inventory import Inventory from bookstore.inventory import Inventory
from loguru import logger
from ui.app import App from ui.app import App
# Set up logging
logger.add(
Path("logs", "bookstore.log"),
format="{time} | {level} | {message}",
level="INFO",
rotation="1 MB",
compression="zip",
)
@logger.catch
def main() -> None: def main() -> None:
db_path = Path("db.sqlite3") db_path = Path("db.sqlite3")
inventory = Inventory(db_path) inventory = Inventory(db_path)