Skip to content
Open
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
180 changes: 110 additions & 70 deletions backend/utils/db.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,9 @@
"""
import os
import sqlite3
from typing import Type
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"""
Expand All @@ -22,93 +24,131 @@ def get_db_connection(db_file_path:str="data/db.sqlite3"):

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()
return [Snack(**record) for record in records]
except sqlite3.Error as e:
raise DatabaseError(f"Database error when fetching 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 sqlite3.Error as e:
if(isinstance(e,ConnectionError)):
raise ConnectionError(f"Error when connecting to database: {str(e)}")
raise DatabaseError(f"Database error when fetching snack {sku}: {str(e)}")
Comment on lines +59 to +68

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you can do something like this which makes it more readable

Suggested change
except sqlite3.Error as e:
if(isinstance(e,ConnectionError)):
raise ConnectionError(f"Error when connecting to database: {str(e)}")
raise DatabaseError(f"Database error when fetching snack {sku}: {str(e)}")
except ConnectionError as e:
raise ConnectionError(f"Error when connecting to database: {str(e)}")
except Exception 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()
return Snack(**record)
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 not isinstance(snack.sku, str):
raise RecordNotFoundError(f"{snack.sku} is not a string")
if not isinstance(snack.name, str):
raise RecordNotFoundError(f"{snack.name} is not a string")
if not isinstance(snack.quantity, int):
raise RecordNotFoundError(f"{snack.quantity} is not an int")
if not isinstance(snack.price, float):
raise RecordNotFoundError(f"{snack.price} is not a float")
if not isinstance(snack.description, str):
raise RecordNotFoundError(f"{snack.description} is not a string")
if not isinstance(snack.category, str):
raise RecordNotFoundError(f"{snack.category} is not a string")
if not isinstance(snack.photo_url, str):
raise RecordNotFoundError(f"{snack.photo_url} is not a string")

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think we need to check the types here because the Pydantic models should be handling that for us.

return Snack(**record)
if record is none:
raise RecordNotFoundError(f"Error when creating snack: {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()
return Snack(**record)
except sqlite3.Error as e:
raise DatabaseError(f"Database error when deleting snack {sku}: {str(e)}")


# Initialize the database and create tables
Expand Down
19 changes: 19 additions & 0 deletions backend/utils/exceptions.py
Original file line number Diff line number Diff line change
@@ -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