2025-03-28 10:29:43 +00:00

164 lines
5.1 KiB
Python

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