commit 52ee1d7a8bdc36dacf555cf46c1fa3cfc163480d Author: cutsweettea Date: Tue Sep 30 15:25:40 2025 -0400 yessir diff --git a/book_stuff/book.py b/book_stuff/book.py new file mode 100644 index 0000000..0c546bd --- /dev/null +++ b/book_stuff/book.py @@ -0,0 +1,107 @@ +""" +This file is the beginnings of a Book class. However, it isn't very well-coded. +We want to harden it. We want to enforce the following rules: + +- Title must not be blank, and cannot be longer than 255 characters. +- Authors is a list of names. None of the names should be blank or over 255 characters. + Names should not have any characters except for alphabetical ones. +- ISBN is a string, but it can only contain numbers and there must be 13 of them. +- Cost cannot be negative. + +We are going to practice "test-driven development". This means we will write +the test cases for our code before we write our code. Enough of the code is +already given to you to show you how to use it. Don't update the code until +you have written tests to check for all of the above criteria. When your code passes +your tests, have your neighbor email you their tests and see if theirs passes as well. +If not, modify your code accordingly. +""" +class Book: + def __init__(self, title: str, authors: list[str], isbn: str, cost: float): + self.set_title(title) + self.set_authors(authors) + self.set_isbn(isbn) + self.set_cost(cost) + + # --- Title --- + def get_title(self) -> str: + return self._title + + def set_title(self, title: str) -> None: + # check if title is string + if not isinstance(title, str): + raise TypeError(f'title must be of type str, not {type(title).__name__}') + + # check if title is blank or if the title length is greater than 255 characters + title_len = len(title) + if title_len < 1 or title_len > 255: + raise TypeError(f'title\'s length must be between 1 and 255, not {title_len}') + + self._title = title + + # --- Authors --- + def get_authors(self) -> list[str]: + return self._authors + + def set_authors(self, authors: list[str]) -> None: + # check if authors is a list + if not isinstance(authors, list): + raise TypeError(f'authors must be of type list, not {type(authors).__name__}') + + # check if authors is empty + authors_len = len(authors) + if authors_len == 0: + raise ValueError('authors list must not be empty') + + for author in authors: + # check if each author is a string + if not isinstance(author, str): + raise TypeError(f'each author within authors must be of type str, not {type(author).__name__}') + + # check if each author is alphanumeric + for author_name_part in author.split(): + # splitting by space to check each part of the author's name, spaces aren't alpha so no author.isalpha() + if not author_name_part.isalpha(): + raise ValueError(f'each part of the author\'s name within authors must be alphanumeric') + + # check if each author's length is greater than 255 + author_len = len(author) + if author_len > 255: + raise ValueError(f'each author\'s length within authors must be less than 256 characters') + + self._authors = authors + + # --- ISBN --- + def get_isbn(self) -> str: + return self._isbn + + def set_isbn(self, isbn: str) -> None: + # check is isbn is 13 characters + isbn_len = len(isbn) + if isbn_len != 13: + raise ValueError(f'isbn\'s length must be 13 digits, not {isbn_len}') + + # check if each character is a number + if not isbn.isdigit(): + raise ValueError('each character in isbn must be a digit') + + self._isbn = isbn + + # --- Cost --- + def get_cost(self) -> float: + return self._cost + + def set_cost(self, cost: float) -> None: + # check if cost is float + if not isinstance(cost, float): + raise ValueError(f'cost must be of type float, not {type(cost).__name__}') + + # check if cost is negative + if cost < 0: + raise ValueError("cost cannot be negative") + + self._cost = cost + + # --- String representation --- + def __str__(self) -> str: + authors = ", ".join(self._authors) + return f"'{self._title}' by {authors} (ISBN: {self._isbn}, Cost: ${self._cost:.2f})" \ No newline at end of file diff --git a/book_stuff/test_book.py b/book_stuff/test_book.py new file mode 100644 index 0000000..0b4325f --- /dev/null +++ b/book_stuff/test_book.py @@ -0,0 +1,96 @@ +import pytest + +from book import Book + +_default_title = 'My Boke' +_default_authors = ['Eliott Wootton', 'John Smith'] +_default_isbn = '1234567890123' +_default_cost = 14.99 + +@pytest.fixture +def valid_book(): + return Book(_default_title, _default_authors, _default_isbn, _default_cost) + +# test if book setter works +def test_create_valid_book(valid_book: Book): + assert valid_book.get_title() == _default_title + assert valid_book.get_authors() == _default_authors + assert valid_book.get_isbn() == _default_isbn + assert valid_book.get_cost() == _default_cost + +# test if title setter works +def test_update_title(valid_book: Book): + valid_book.set_title('My Book') + assert valid_book.get_title() == 'My Book' + +# test if authors setter works +def test_update_authors(valid_book: Book): + valid_book.set_authors(['John Smith', 'Jane Smith']) + assert valid_book.get_authors() == ['John Smith', 'Jane Smith'] + +# test if isbn setter works +def test_update_isbn(valid_book: Book): + valid_book.set_isbn('3210987654321') + assert valid_book.get_isbn() == '3210987654321' + +# test is cost setter works +def test_update_cost(valid_book: Book): + valid_book.set_cost(9.99) + assert valid_book.get_cost() == 9.99 + +# test if repr works +def test_repr_str(valid_book): + rep = str(valid_book) + assert _default_title in rep + assert ', '.join(_default_authors) in rep + assert _default_isbn in rep + assert str(_default_cost) in rep + +""" +- Title must not be blank, and cannot be longer than 255 characters. +- Authors is a list of names. None of the names should be blank or over 255 characters. + Names should not have any characters except for alphabetical ones. +- ISBN is a string, but it can only contain numbers and there must be 13 of them. +- Cost cannot be negative. +""" + +# test if empty, length > 255, and if string +@pytest.mark.parametrize("invalid_title", [ + "", + "A" * 256, + 6.7 +]) +def test_invalid_title_raises(invalid_title: str): + with pytest.raises((ValueError, TypeError)): + Book(invalid_title, _default_authors, _default_isbn, _default_cost) + +# test if empty, is array, each author's name is str, each author's name > 255 and each author's name alpha +@pytest.mark.parametrize("invalid_authors", [ + [], + 'Eliott Wootton', + [41, 'Charleston White'], + ['LeBron James', 'A'*256], + ['3liott W00tt0n', 'hello world'] +]) +def test_invalid_authors_raised(invalid_authors: str): + with pytest.raises((ValueError, TypeError)): + Book(_default_title, invalid_authors, _default_isbn, _default_cost) + +# test if string, only contains numbers and length is 13 +@pytest.mark.parametrize("invalid_isbn", [ + 127, + '123456789o123', + '12345678901234' +]) +def test_invalid_isbn_raises(invalid_isbn: str): + with pytest.raises((ValueError, TypeError)): + Book(_default_title, _default_authors, invalid_isbn, _default_cost) + +# test if float and not negative +@pytest.mark.parametrize("invalid_cost", [ + '14.99', + -14.99 +]) +def test_invalid_cost_raises(invalid_cost: str): + with pytest.raises((ValueError, TypeError)): + Book(_default_title, _default_authors, _default_isbn, invalid_cost) \ No newline at end of file