diff --git a/helpers/book_helpers.py b/helpers/book_helpers.py new file mode 100644 index 0000000..28c23fe --- /dev/null +++ b/helpers/book_helpers.py @@ -0,0 +1,128 @@ +from typing import Optional, Dict, Union, List +import re +from sqlalchemy.orm import Session +from models.book import Book +from schemas.book import BookCreate, BookUpdate + +def validate_isbn(isbn: str) -> bool: + """ + Validate ISBN format (ISBN-10 or ISBN-13). + + Args: + isbn: ISBN string to validate + + Returns: + bool: True if valid ISBN format, False otherwise + """ + isbn = isbn.replace("-", "").replace(" ", "") + if len(isbn) == 10: + # ISBN-10 validation + if not isbn[:-1].isdigit() and isbn[-1].lower() != 'x': + return False + total = 0 + for i in range(9): + total += int(isbn[i]) * (10 - i) + check = 11 - (total % 11) + return str(check) == isbn[-1] or (check == 10 and isbn[-1].lower() == 'x') + elif len(isbn) == 13: + # ISBN-13 validation + if not isbn.isdigit(): + return False + total = 0 + for i in range(12): + if i % 2 == 0: + total += int(isbn[i]) + else: + total += int(isbn[i]) * 3 + check = 10 - (total % 10) + if check == 10: + check = 0 + return str(check) == isbn[-1] + return False + +def get_book_by_isbn(db: Session, isbn: str) -> Optional[Book]: + """ + Retrieve a book by its ISBN. + + Args: + db: Database session + isbn: ISBN to search for + + Returns: + Book object if found, None otherwise + """ + return db.query(Book).filter(Book.isbn == isbn).first() + +def search_books_by_title(db: Session, title: str, limit: int = 10) -> List[Book]: + """ + Search for books by title using partial matching. + + Args: + db: Database session + title: Title to search for + limit: Maximum number of results to return + + Returns: + List of matching Book objects + """ + return db.query(Book).filter(Book.title.ilike(f"%{title}%")).limit(limit).all() + +def create_book_safely(db: Session, book_data: BookCreate) -> Union[Book, Dict[str, str]]: + """ + Create a new book with validation and error handling. + + Args: + db: Database session + book_data: Book data for creation + + Returns: + Book object if created successfully, error dict otherwise + """ + # Validate required fields + if not book_data.title or not book_data.title.strip(): + return {"error": "Title is required"} + + if not book_data.author or not book_data.author.strip(): + return {"error": "Author is required"} + + # Validate ISBN + if not validate_isbn(book_data.isbn): + return {"error": "Invalid ISBN format"} + + # Check for duplicate ISBN + existing_book = get_book_by_isbn(db, book_data.isbn) + if existing_book: + return {"error": "Book with this ISBN already exists"} + + # Create the book + db_book = Book( + title=book_data.title.strip(), + author=book_data.author.strip(), + description=book_data.description.strip() if book_data.description else None, + isbn=book_data.isbn.replace("-", "").replace(" ", "") + ) + + db.add(db_book) + db.commit() + db.refresh(db_book) + + return db_book + +def format_book_response(book: Book) -> Dict[str, str]: + """ + Format a book object into a standardized response format. + + Args: + book: Book object to format + + Returns: + Dictionary with formatted book data + """ + return { + "title": book.title, + "author": book.author, + "description": book.description or "", + "isbn": f"{book.isbn[:3]}-{book.isbn[3:4]}-{book.isbn[4:6]}-{book.isbn[6:12]}-{book.isbn[12:]}" + if len(book.isbn) == 13 + else f"{book.isbn[:1]}-{book.isbn[1:3]}-{book.isbn[3:9]}-{book.isbn[9:]}" + } \ No newline at end of file