diff --git a/backend/utils/db.py b/backend/utils/db.py index 23e7b46..fda6ac5 100644 --- a/backend/utils/db.py +++ b/backend/utils/db.py @@ -12,103 +12,147 @@ import os import sqlite3 from models.snack import Snack, SnackCreateSchema, SnackUpdateSchema +from exceptions import DatabaseError, ConnectionError, RecordNotFoundError, DuplicateRecordError, DatabaseInitError def get_db_connection(db_file_path:str="data/db.sqlite3"): """Creates and returns a SQLite database connection""" - connection = sqlite3.connect(db_file_path) - connection.row_factory = sqlite3.Row # Allows accessing columns by name - return connection + try: + connection = sqlite3.connect(db_file_path) + connection.row_factory = sqlite3.Row # Allows accessing columns by name + return connection + except sqlite3.Error as e: + raise DatabaseError(f"Error creating database connection: {str(e)}") def init_db(db_file_path: str = "data/db.sqlite3"): """Initialize the database with schema""" - os.makedirs(os.path.dirname(db_file_path), exist_ok=True) - with open('data/schema.sql') as f: - schema = f.read() - with get_db_connection() as conn: - conn.executescript(schema) - + try: + os.makedirs(os.path.dirname(db_file_path), exist_ok=True) + with open('data/schema.sql') as f: + schema = f.read() + with get_db_connection() as conn: + conn.executescript(schema) + except sqlite3.Error as e: + raise DatabaseInitError(f"Error when initializing database: {str(e)}") + def get_inventory() -> list[Snack]: """Returns all snacks in the database""" - with get_db_connection() as conn: - cursor = conn.cursor() - cursor.execute("SELECT * FROM snacks") - records = cursor.fetchall() - return [Snack(**record) for record in records] + try: + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT * FROM snacks") + records = cursor.fetchall() + if not records: + raise RecordNotFoundError("No snacks in database") + return [Snack(**record) for record in records] + except ConnectionError as e: + raise ConnectionError(f"Error when connecting to database: {str(e)}") + except sqlite3.Error as e: + raise DatabaseError(f"Database error when fetching all snacks: {str(e)}") def get_snack(sku: str) -> Snack: """Returns a single snack by SKU""" - with get_db_connection() as conn: - cursor = conn.cursor() - cursor.execute("SELECT * FROM snacks WHERE sku = ?", (sku,)) - record = cursor.fetchone() - return Snack(**record) - + try: + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute("SELECT * FROM snacks WHERE sku = ?", (sku,)) + record = cursor.fetchone() + if record is None: + raise RecordNotFoundError(f"No snack found with SKU: {sku}") + return Snack(**record) + except ConnectionError as e: + raise ConnectionError(f"Error when connecting to database: {str(e)}") + except sqlite3.Error as e: + raise DatabaseError(f"Database error when fetching snack {sku}: {str(e)}") + def delete_snack(sku: str) -> Snack: """Removes a snack from the database""" - with get_db_connection() as conn: - cursor = conn.cursor() - cursor.execute(""" - DELETE FROM snacks - WHERE sku = ? - RETURNING * - """, (sku,)) - record = cursor.fetchone() - return Snack(**record) - - + try: + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute(""" + DELETE FROM snacks + WHERE sku = ? + RETURNING * + """, (sku,)) + record = cursor.fetchone() + if record is None: + raise RecordNotFoundError(f"Didn't delete snack {sku} successfully") + return Snack(**record) + except ConnectionError as e: + raise ConnectionError(f"Error when connecting to database: {str(e)}") + except sqlite3.Error as e: + raise DatabaseError(f"Database error when deleting snack {sku}: {str(e)}") + + def create_snack(snack: SnackCreateSchema) -> Snack: """Creates a new snack in the database""" - with get_db_connection() as conn: - cursor = conn.cursor() - cursor.execute(""" - INSERT INTO snacks - (sku, name, quantity, price, description, category, photo_url) - VALUES - (?, ?, ?, ?, ?, ?, ?) - RETURNING * - """, ( - snack.sku, - snack.name, - snack.quantity if snack.quantity is not None else 1, - snack.price, - snack.description, - snack.category, - snack.photo_url - )) - record = cursor.fetchone() - return Snack(**record) + try: + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute(""" + INSERT INTO snacks + (sku, name, quantity, price, description, category, photo_url) + VALUES + (?, ?, ?, ?, ?, ?, ?) + RETURNING * + """, ( + snack.sku, + snack.name, + snack.quantity if snack.quantity is not None else 1, + snack.price, + snack.description, + snack.category, + snack.photo_url + )) + record = cursor.fetchone() + if record is None: + raise DatabaseError(f"Error when creating snack") + return Snack(**record) + except DuplicateRecordError as e: + raise DuplicateRecordError(f"This snack already exists: {str(e)}") + except ConnectionError as e: + raise ConnectionError(f"Error when connecting to database: {str(e)}") + except sqlite3.Error as e: + raise DatabaseError(f"Database error when creating snack {snack.sku}: {str(e)}") def update_snack(sku: str, updates: SnackUpdateSchema) -> Snack: """Updates an existing snack in the database""" - with get_db_connection() as conn: - cursor = conn.cursor() - cursor.execute(""" - UPDATE snacks - SET - name = ?, - quantity = ?, - price = ?, - description = ?, - category = ?, - photo_url = ? - WHERE sku = ? - RETURNING * - """, ( - updates.name, - updates.quantity, - updates.price, - updates.description, - updates.category, - updates.photo_url, - sku - )) - record = cursor.fetchone() - return Snack(**record) + try: + with get_db_connection() as conn: + cursor = conn.cursor() + cursor.execute(""" + UPDATE snacks + SET + name = ?, + quantity = ?, + price = ?, + description = ?, + category = ?, + photo_url = ? + WHERE sku = ? + RETURNING * + """, ( + updates.name, + updates.quantity, + updates.price, + updates.description, + updates.category, + updates.photo_url, + sku + )) + record = cursor.fetchone() + if record is None: + raise RecordNotFoundError(f"Snack with SKU: {sku} doesn't exist") + return Snack(**record) + except ConnectionError as e: + raise ConnectionError(f"Error when connecting to database: {str(e)}") + except sqlite3.Error as e: + raise DatabaseError(f"Database error when updating snack {sku}: {str(e)}") # Initialize the database and create tables diff --git a/backend/utils/exceptions.py b/backend/utils/exceptions.py new file mode 100644 index 0000000..8bd5646 --- /dev/null +++ b/backend/utils/exceptions.py @@ -0,0 +1,19 @@ +class DatabaseError(Exception): + """Base exception for database errors""" + pass + +class ConnectionError(DatabaseError): + """Failed to connect to database""" + pass + +class RecordNotFoundError(DatabaseError): + """Requested record does not exist""" + pass + +class DuplicateRecordError(DatabaseError): + """Record with this identifier already exists""" + pass + +class DatabaseInitError(DatabaseError): + """Failed to initialize database""" + pass \ No newline at end of file