Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
73 changes: 52 additions & 21 deletions samples/book-app-project/book_app.py
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
import sys
from books import BookCollection
from books import Book, BookCollection


# Global collection instance
collection = BookCollection()


def show_books(books):
def show_books(books: list[Book]) -> None:
"""Display books in a user-friendly format."""
if not books:
print("No books found.")
Expand All @@ -21,27 +21,34 @@ def show_books(books):
print()


def handle_list():
def handle_list() -> None:
books = collection.list_books()
show_books(books)


def handle_add():
def handle_add() -> None:
print("\nAdd a New Book\n")

title = input("Title: ").strip()
author = input("Author: ").strip()
year_str = input("Year: ").strip()

if not year_str:
print("\nError: Year cannot be empty.\n")
return

try:
year = int(year_str) if year_str else 0
year = int(year_str)
if year < 1 or year > 2100:
print("\nError: Year must be between 1 and 2100.\n")
return
collection.add_book(title, author, year)
print("\nBook added successfully.\n")
except ValueError as e:
print(f"\nError: {e}\n")


def handle_remove():
def handle_remove() -> None:
print("\nRemove a Book\n")

title = input("Enter the title of the book to remove: ").strip()
Expand All @@ -50,7 +57,7 @@ def handle_remove():
print("\nBook removed if it existed.\n")


def handle_find():
def handle_find() -> None:
print("\nFind Books by Author\n")

author = input("Author name: ").strip()
Expand All @@ -59,39 +66,63 @@ def handle_find():
show_books(books)


def show_help():
def handle_search() -> None:
print("\nSearch Books\n")

query = input("Search query (title or author): ").strip()
if not query:
print("\nError: Search query cannot be empty.\n")
return

books = collection.search_books(query)
show_books(books)


def handle_list_unread() -> None:
books = collection.get_unread_books()
show_books(books)


def show_help() -> None:
print("""
Book Collection Helper

Commands:
list - Show all books
unread - Show only unread books
add - Add a new book
remove - Remove a book by title
find - Find books by author
find - Find books by author (exact match)
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The help text says find does an exact match, but BookCollection.find_by_author() now performs a partial substring match. Please update the CLI help text (or change the implementation) so user-facing documentation matches actual behavior.

Suggested change
find - Find books by author (exact match)
find - Find books by author (partial match)

Copilot uses AI. Check for mistakes.
search - Search books by title or author (partial match)
help - Show this help message
""")


def main():
COMMANDS = {
"list": handle_list,
"unread": handle_list_unread,
"add": handle_add,
"remove": handle_remove,
"find": handle_find,
"search": handle_search,
"help": show_help,
}


def main() -> None:
if len(sys.argv) < 2:
show_help()
return

command = sys.argv[1].lower()
handler = COMMANDS.get(command)

if command == "list":
handle_list()
elif command == "add":
handle_add()
elif command == "remove":
handle_remove()
elif command == "find":
handle_find()
elif command == "help":
show_help()
if handler:
handler()
else:
print("Unknown command.\n")
print(f"Unknown command: '{command}'\n")
show_help()
sys.exit(1)


if __name__ == "__main__":
Expand Down
16 changes: 14 additions & 2 deletions samples/book-app-project/books.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ def save_books(self):
json.dump([asdict(b) for b in self.books], f, indent=2)

def add_book(self, title: str, author: str, year: int) -> Book:
if year < 1 or year > 2100:
raise ValueError(f"Year must be between 1 and 2100, got {year}.")
Comment on lines 38 to +40
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PR description focuses on improving find_by_author(), but this PR also introduces new behavior/API changes (year range validation in add_book) and new methods (search_books, get_unread_books) plus CLI changes/tests. Please update the PR description/scope accordingly (or split into separate PRs) so reviewers can assess intent, compatibility, and release notes accurately.

Copilot uses AI. Check for mistakes.
book = Book(title=title, author=author, year=year)
self.books.append(book)
self.save_books()
Expand Down Expand Up @@ -68,5 +70,15 @@ def remove_book(self, title: str) -> bool:
return False

def find_by_author(self, author: str) -> List[Book]:
"""Find all books by a given author."""
return [b for b in self.books if b.author.lower() == author.lower()]
"""Find all books where author contains the given string (case-insensitive partial match)."""
normalized = author.casefold()
return [b for b in self.books if normalized in b.author.casefold()]

def search_books(self, query: str) -> List[Book]:
"""Find books where query matches (partial, case-insensitive) title or author."""
q = query.lower()
return [b for b in self.books if q in b.title.lower() or q in b.author.lower()]
Comment on lines +79 to +80
Copy link

Copilot AI Apr 10, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

search_books() is documented as case-insensitive, but it uses .lower() rather than Unicode-aware .casefold(). Since find_by_author() was updated to casefold for correctness (e.g., ß/ss), search_books() will behave inconsistently for some Unicode queries. Consider using casefold() for both the query and the book fields to ensure correct case-insensitive matching.

Suggested change
q = query.lower()
return [b for b in self.books if q in b.title.lower() or q in b.author.lower()]
q = query.casefold()
return [b for b in self.books if q in b.title.casefold() or q in b.author.casefold()]

Copilot uses AI. Check for mistakes.

def get_unread_books(self) -> List[Book]:
"""Return all books that have not been marked as read."""
return [b for b in self.books if not b.read]
Loading
Loading