""" 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})"