107 lines
4.2 KiB
Python
107 lines
4.2 KiB
Python
"""
|
|
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})" |