diff --git a/helpers/book_helpers.py b/helpers/book_helpers.py new file mode 100644 index 0000000..bfb786f --- /dev/null +++ b/helpers/book_helpers.py @@ -0,0 +1,164 @@ +from typing import List, Dict, Optional, Union, Any +from datetime import datetime +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 (supports both ISBN-10 and 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 search_books( + db: Session, + title: Optional[str] = None, + author: Optional[str] = None, + genre: Optional[str] = None, + publisher: Optional[str] = None, + available_only: bool = False +) -> List[Book]: + """ + Search books with multiple optional filters. + + Args: + db: Database session + title: Optional title to search for + author: Optional author to search for + genre: Optional genre to filter by + publisher: Optional publisher to filter by + available_only: If True, return only available books + + Returns: + List of books matching the search criteria + """ + query = db.query(Book) + + if title: + query = query.filter(Book.title.ilike(f"%{title}%")) + if author: + query = query.filter(Book.author.ilike(f"%{author}%")) + if genre: + query = query.filter(Book.genre == genre) + if publisher: + query = query.filter(Book.publisher == publisher) + if available_only: + query = query.filter(Book.is_available == True) + + return query.all() + +def get_book_stats(db: Session) -> Dict[str, Any]: + """ + Get statistical information about books in the database. + + Args: + db: Database session + + Returns: + Dictionary containing various statistics about books + """ + total_books = db.query(Book).count() + available_books = db.query(Book).filter(Book.is_available == True).count() + genres = db.query(Book.genre).distinct().all() + languages = db.query(Book.language).distinct().all() + avg_pages = db.query(func.avg(Book.page_count)).scalar() + + return { + "total_books": total_books, + "available_books": available_books, + "total_genres": len(genres), + "genres": [g[0] for g in genres], + "languages": [l[0] for l in languages], + "average_page_count": round(avg_pages if avg_pages else 0, 2) + } + +def validate_book_data(book_data: Union[BookCreate, BookUpdate]) -> Dict[str, str]: + """ + Validate book data before creation or update. + + Args: + book_data: Book data to validate + + Returns: + Dictionary of validation errors, empty if validation passes + """ + errors = {} + + if hasattr(book_data, 'isbn') and book_data.isbn: + if not validate_isbn(book_data.isbn): + errors['isbn'] = "Invalid ISBN format" + + if hasattr(book_data, 'publication_year'): + current_year = datetime.now().year + if book_data.publication_year > current_year: + errors['publication_year'] = "Publication year cannot be in the future" + + if hasattr(book_data, 'page_count'): + if book_data.page_count <= 0: + errors['page_count'] = "Page count must be positive" + + return errors + +def get_similar_books(db: Session, book_id: int, limit: int = 5) -> List[Book]: + """ + Find similar books based on genre and author. + + Args: + db: Database session + book_id: ID of the reference book + limit: Maximum number of similar books to return + + Returns: + List of similar books + """ + reference_book = db.query(Book).filter(Book.id == book_id).first() + if not reference_book: + return [] + + similar_books = db.query(Book).filter( + Book.id != book_id, + Book.genre == reference_book.genre, + Book.author == reference_book.author + ).limit(limit).all() + + if len(similar_books) < limit: + # If not enough books by same author, add books from same genre + additional_books = db.query(Book).filter( + Book.id != book_id, + Book.genre == reference_book.genre, + Book.author != reference_book.author + ).limit(limit - len(similar_books)).all() + similar_books.extend(additional_books) + + return similar_books \ No newline at end of file