128 lines
3.7 KiB
Python
128 lines
3.7 KiB
Python
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:]}"
|
|
} |