This commit is contained in:
cutsweettea
2025-09-30 15:25:40 -04:00
commit 52ee1d7a8b
2 changed files with 203 additions and 0 deletions

107
book_stuff/book.py Normal file
View File

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

96
book_stuff/test_book.py Normal file
View File

@@ -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)