diff --git a/helpers/book_helpers.py b/helpers/book_helpers.py new file mode 100644 index 0000000..4866b5c --- /dev/null +++ b/helpers/book_helpers.py @@ -0,0 +1,127 @@ +from typing import List, Dict, Optional, Union, Any +from datetime import datetime +import re +from sqlalchemy.orm import Session +from pydantic import ValidationError + +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 ISBN format is valid, False otherwise + """ + # Remove hyphens and spaces + isbn = isbn.replace('-', '').replace(' ', '') + + if len(isbn) == 10: + # ISBN-10 validation + if not isbn[:-1].isdigit(): + return False + check_digit = 'X' if isbn[-1].upper() == 'X' else isbn[-1] + if not (check_digit.isdigit() or check_digit == 'X'): + return False + return True + elif len(isbn) == 13: + # ISBN-13 validation + if not isbn.isdigit(): + return False + return isbn.startswith('978') or isbn.startswith('979') + return False + +def format_book_price(price: int) -> str: + """ + Format book price from cents to currency string. + + Args: + price: Price in cents + + Returns: + str: Formatted price string + """ + return f"${price/100:.2f}" + +def get_books_by_author(db: Session, author: str) -> List[Any]: + """ + Get all books by a specific author. + + Args: + db: Database session + author: Author name to search for + + Returns: + List of Book objects by the author + """ + return db.query(Book).filter(Book.author == author).all() + +def search_books( + db: Session, + search_term: str, + include_unavailable: bool = False +) -> List[Any]: + """ + Search books by title, author, or ISBN. + + Args: + db: Database session + search_term: Term to search for + include_unavailable: Whether to include unavailable books + + Returns: + List of matching Book objects + """ + query = db.query(Book).filter( + (Book.title.ilike(f"%{search_term}%")) | + (Book.author.ilike(f"%{search_term}%")) | + (Book.isbn == search_term) + ) + + if not include_unavailable: + query = query.filter(Book.is_available == True) + + return query.all() + +def validate_publication_year(year: int) -> bool: + """ + Validate if publication year is within acceptable range. + + Args: + year: Publication year to validate + + Returns: + bool: True if year is valid, False otherwise + """ + current_year = datetime.now().year + return 1800 <= year <= current_year + 1 + +def process_book_data(book_data: Dict[str, Any]) -> Dict[str, Any]: + """ + Process and validate book data before creation/update. + + Args: + book_data: Dictionary containing book data + + Returns: + Dict with processed and validated data + """ + processed_data = book_data.copy() + + # Validate ISBN + if 'isbn' in processed_data and not validate_isbn(processed_data['isbn']): + raise ValidationError("Invalid ISBN format") + + # Validate publication year + if 'publication_year' in processed_data: + if not validate_publication_year(processed_data['publication_year']): + raise ValidationError("Invalid publication year") + + # Ensure title and author are stripped of whitespace + if 'title' in processed_data: + processed_data['title'] = processed_data['title'].strip() + if 'author' in processed_data: + processed_data['author'] = processed_data['author'].strip() + + return processed_data \ No newline at end of file