Skip to main content

Overview

The loans module manages the complete book circulation lifecycle: checkout, return, renewal, and loan tracking. All loans have a 14-day lending period.

Checkout Books

async def checkout_books(books: list, user_id: int) -> None:
    """Mark each book as 'Checked Out' and create a loan record.
    
    Due in 14 days.
    """

Parameters

books
list[dict]
required
List of book objects to checkout. Each book dict must contain an isbn key.Example:
books = [
    {"isbn": "9780132350884", "title": "Clean Code", ...},
    {"isbn": "9780201633610", "title": "Design Patterns", ...}
]
user_id
int
required
Database ID of the user checking out the books

Returns

None
None
This function does not return a value. All operations complete successfully or raise an exception.

Side Effects

  1. Updates Book Status: Sets each book’s status to “Checked Out”
  2. Creates Loan Records: Inserts one loan record per book with:
    • user_id: The borrowing user
    • isbn: The book identifier
    • checked_out: Current timestamp (auto-set)
    • due_date: Current date + 14 days
    • returned: 0 (active loan)

Implementation Details

due = datetime.now() + timedelta(days=14)

for book in books:
    # Update book status
    conn.execute(
        "UPDATE books SET status = 'Checked Out' WHERE isbn = ?",
        (book["isbn"],)
    )
    # Create loan record
    conn.execute(
        "INSERT INTO loans (user_id, isbn, due_date) VALUES (?, ?, ?)",
        (user_id, book["isbn"], due)
    )

Return Book

async def return_book(isbn: str) -> bool:
    """Mark a book as Available and close its open loan."""

Parameters

isbn
string
required
ISBN of the book to return

Returns

success
bool
  • True: Book was successfully returned
  • False: No active loan found for this ISBN

Operation Details

  1. Find Active Loan: Queries for the most recent loan where:
    • isbn matches the parameter
    • returned = 0 (loan is active)
    • Orders by checked_out DESC to get the latest loan
  2. Close Loan: If found, updates the loan record:
    • Sets returned = 1
    • Sets returned_date to current timestamp
  3. Update Book: Sets book status back to “Available”

Error Cases

Returns False when:
  • No active loan exists for the ISBN
  • Book was never checked out
  • Book was already returned

Renew Book

async def renew_book(loan_id: int) -> bool:
    """Extend the due date of an active loan by 14 days from today."""

Parameters

loan_id
int
required
Database ID of the loan record to renew

Returns

success
bool
  • True: Loan was successfully renewed
  • False: Loan not found or already returned

Renewal Logic

  1. Verify Active Loan: Checks that loan exists and returned = 0
  2. Calculate New Due Date: new_due_date = today + 14 days
  3. Update Record: Sets due_date to the new date
Important: Renewal is based on the current date, not the original due date. This means:
  • Renewing early gives 14 days from today
  • Overdue books would get 14 days from today (if allowed)

Business Logic Recommendations

Consider these rules in your UI:
  • Disable renewal for overdue books
  • Limit number of renewals per loan
  • Only allow renewals within certain timeframes

Get User Loans

async def get_user_loans(user_id: int) -> list:
    """Return all loans for a user, joined with book info."""

Parameters

user_id
int
required
Database ID of the user whose loans to retrieve

Returns

loans
list[dict]
List of all loan records for the user, ordered by checkout date (newest first)

SQL Query

The function executes a JOIN query:
SELECT
    l.id, l.returned, l.due_date, l.returned_date,
    b.title, b.author, b.cover
FROM loans l
JOIN books b ON l.isbn = b.isbn
WHERE l.user_id = ?
ORDER BY l.checked_out DESC

Date Parsing

Datetime fields are automatically parsed:
  • SQLite stores datetimes as ISO 8601 strings
  • Function attempts to parse due_date and returned_date as datetime objects
  • Falls back to original string value if parsing fails

Use Cases

  • My Books Page: Display current checkouts and history
  • Overdue Tracking: Filter by due_date < now()
  • Renewal Interface: Show active loans with renewal options
  • Reading History: Filter by returned = True

Build docs developers (and LLMs) love