Skip to content
Open
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
20 changes: 7 additions & 13 deletions tests/test_wave_01.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from viewing_party.party import *
from tests.test_constants import *

@pytest.mark.skip()

def test_create_successful_movie():
# Arrange
movie_title = MOVIE_TITLE_1
Expand All @@ -19,7 +19,7 @@ def test_create_successful_movie():
assert new_movie["genre"] == GENRE_1
assert new_movie["rating"] == pytest.approx(RATING_1)

@pytest.mark.skip()

def test_create_no_title_movie():
# Arrange
movie_title = None
Expand All @@ -32,7 +32,7 @@ def test_create_no_title_movie():
# Assert
assert new_movie is None

@pytest.mark.skip()

def test_create_no_genre_movie():
# Arrange
movie_title = "Title A"
Expand All @@ -45,7 +45,6 @@ def test_create_no_genre_movie():
# Assert
assert new_movie is None

@pytest.mark.skip()
def test_create_no_rating_movie():
# Arrange
movie_title = "Title A"
Expand All @@ -58,7 +57,6 @@ def test_create_no_rating_movie():
# Assert
assert new_movie is None

@pytest.mark.skip()
def test_adds_movie_to_user_watched():
# Arrange
movie = {
Expand All @@ -79,7 +77,7 @@ def test_adds_movie_to_user_watched():
assert updated_data["watched"][0]["genre"] == GENRE_1
assert updated_data["watched"][0]["rating"] == RATING_1

@pytest.mark.skip()

def test_adds_movie_to_user_watchlist():
# Arrange
movie = {
Expand All @@ -100,7 +98,6 @@ def test_adds_movie_to_user_watchlist():
assert updated_data["watchlist"][0]["genre"] == GENRE_1
assert updated_data["watchlist"][0]["rating"] == RATING_1

@pytest.mark.skip()
def test_moves_movie_from_watchlist_to_empty_watched():
# Arrange
janes_data = {
Expand All @@ -118,13 +115,11 @@ def test_moves_movie_from_watchlist_to_empty_watched():
# Assert
assert len(updated_data["watchlist"]) == 0
assert len(updated_data["watched"]) == 1

raise Exception("Test needs to be completed.")
assert updated_data["watched"][0]['title'] == MOVIE_TITLE_1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice assertion for the title! In this case where the test is checking individual keys of a dictionary, I recommend including assertions for all of the relevant keys.

    watched_movie = updated_data["watched"][0]
    assert watched_movie["title”] == MOVIE_TITLE_1
    assert watched_movie["rating"] == RATING_1
    assert watched_movie["genre"] == GENRE_1

# *******************************************************************************************
# ****** Add assertions here to test that the correct movie was added to "watched" **********
# *******************************************************************************************

@pytest.mark.skip()
def test_moves_movie_from_watchlist_to_watched():
# Arrange
movie_to_watch = HORROR_1
Expand All @@ -142,13 +137,12 @@ def test_moves_movie_from_watchlist_to_watched():
# Assert
assert len(updated_data["watchlist"]) == 1
assert len(updated_data["watched"]) == 2

raise Exception("Test needs to be completed.")
assert updated_data["watched"][1]['title'] == movie_to_watch["title"]

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The README isn't explicit about what order we should add movies to the list watched. If we want our tests to be independent from a specific implementation that adds movies to a particular location in the watched list, we could use the in keyword to check if the variable movie_to_watch is in the watched list instead of looking at a specific index. By checking for the whole dictionary, there's a side benefit that we don't need to check each key independently:

assert movie_to_watch in updated_data["watched"]

# Another option:
assert HORROR_1 in updated_data["watched"]


# *******************************************************************************************
# ****** Add assertions here to test that the correct movie was added to "watched" **********
# *******************************************************************************************

@pytest.mark.skip()
def test_does_nothing_if_movie_not_in_watchlist():
# Arrange
movie_to_watch = HORROR_1
Expand Down
4 changes: 0 additions & 4 deletions tests/test_wave_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from viewing_party.party import *
from tests.test_constants import *

@pytest.mark.skip()
def test_calculates_watched_average_rating():
# Arrange
janes_data = clean_wave_2_data()
Expand All @@ -14,7 +13,6 @@ def test_calculates_watched_average_rating():
assert average == pytest.approx(3.58333)
assert janes_data == clean_wave_2_data()

@pytest.mark.skip()
def test_empty_watched_average_rating_is_zero():
# Arrange
janes_data = {
Expand All @@ -27,7 +25,6 @@ def test_empty_watched_average_rating_is_zero():
# Assert
assert average == pytest.approx(0.0)

@pytest.mark.skip()
def test_most_watched_genre():
# Arrange
janes_data = clean_wave_2_data()
Expand All @@ -39,7 +36,6 @@ def test_most_watched_genre():
assert popular_genre == "Fantasy"
assert janes_data == clean_wave_2_data()

@pytest.mark.skip()
def test_genre_is_None_if_empty_watched():
# Arrange
janes_data = {
Expand Down
7 changes: 1 addition & 6 deletions tests/test_wave_03.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from viewing_party.party import *
from tests.test_constants import *

@pytest.mark.skip()
def test_my_unique_movies():
# Arrange
amandas_data = clean_wave_3_data()
Expand All @@ -16,7 +15,6 @@ def test_my_unique_movies():
assert INTRIGUE_2 in amandas_unique_movies
assert amandas_data == clean_wave_3_data()

@pytest.mark.skip()
def test_my_not_unique_movies():
# Arrange
amandas_data = clean_wave_3_data()
Expand All @@ -28,7 +26,6 @@ def test_my_not_unique_movies():
# Assert
assert len(amandas_unique_movies) == 0

@pytest.mark.skip()
def test_friends_unique_movies():
# Arrange
amandas_data = clean_wave_3_data()
Expand All @@ -43,7 +40,6 @@ def test_friends_unique_movies():
assert FANTASY_4 in friends_unique_movies
assert amandas_data == clean_wave_3_data()

@pytest.mark.skip()
def test_friends_unique_movies_not_duplicated():
# Arrange
amandas_data = clean_wave_3_data()
Expand All @@ -54,13 +50,12 @@ def test_friends_unique_movies_not_duplicated():

# Assert
assert len(friends_unique_movies) == 3
assert friends_unique_movies.count(INTRIGUE_3) == 1

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice check for INTRIGUE_3 👍 I would suggest checking for each of the movies we expect in friends_unique_movies to ensure only what we expected was added to the list.

assert INTRIGUE_3 in friends_unique_movies
assert HORROR_1 in friends_unique_movies
assert FANTASY_4 in friends_unique_movies


raise Exception("Test needs to be completed.")
# *************************************************************************************************
# ****** Add assertions here to test that the correct movies are in friends_unique_movies **********
# **************************************************************************************************

@pytest.mark.skip()
def test_friends_not_unique_movies():
# Arrange
amandas_data = {
Expand Down
7 changes: 2 additions & 5 deletions tests/test_wave_04.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from viewing_party.party import *
from tests.test_constants import *

@pytest.mark.skip()
def test_get_available_friend_rec():
# Arrange
amandas_data = clean_wave_4_data()
Expand All @@ -12,11 +11,10 @@ def test_get_available_friend_rec():

# Assert
assert len(recommendations) == 2
assert HORROR_1b in recommendations
assert FANTASY_4b in recommendations
assert HORROR_1b in recommendations
assert FANTASY_4b in recommendations
assert amandas_data == clean_wave_4_data()

@pytest.mark.skip()
def test_no_available_friend_recs():
# Arrange
amandas_data = {
Expand All @@ -38,7 +36,6 @@ def test_no_available_friend_recs():
# Assert
assert len(recommendations) == 0

@pytest.mark.skip()
def test_no_available_friend_recs_watched_all():
# Arrange
amandas_data = {
Expand Down
14 changes: 7 additions & 7 deletions tests/test_wave_05.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,6 @@
from viewing_party.party import *
from tests.test_constants import *

@pytest.mark.skip()
def test_new_genre_rec():
# Arrange
sonyas_data = clean_wave_5_data()
Expand All @@ -17,7 +16,6 @@ def test_new_genre_rec():
assert FANTASY_4b in recommendations
assert sonyas_data == clean_wave_5_data()

@pytest.mark.skip()
def test_new_genre_rec_from_empty_watched():
# Arrange
sonyas_data = {
Expand All @@ -38,7 +36,6 @@ def test_new_genre_rec_from_empty_watched():
# Assert
assert len(recommendations) == 0

@pytest.mark.skip()
def test_new_genre_rec_from_empty_friends():
# Arrange
sonyas_data = {
Expand All @@ -53,12 +50,17 @@ def test_new_genre_rec_from_empty_friends():
]
}

raise Exception("Test needs to be completed.")
# Act
recommendations = get_new_rec_by_genre(sonyas_data)

# Assert
assert len(recommendations) == 0
Comment on lines +54 to +57

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great act & assert steps!


#raise Exception("Test needs to be completed.")
# *********************************************************************
# ****** Complete the Act and Assert Portions of theis tests **********
# *********************************************************************

@pytest.mark.skip()
def test_unique_rec_from_favorites():
# Arrange
sonyas_data = clean_wave_5_data()
Expand All @@ -72,7 +74,6 @@ def test_unique_rec_from_favorites():
assert INTRIGUE_2b in recommendations
assert sonyas_data == clean_wave_5_data()

@pytest.mark.skip()
def test_unique_from_empty_favorites():
# Arrange
sonyas_data = {
Expand All @@ -94,7 +95,6 @@ def test_unique_from_empty_favorites():
# Assert
assert len(recommendations) == 0

@pytest.mark.skip()
def test_new_rec_from_empty_friends():
# Arrange
sonyas_data = {
Expand Down
108 changes: 107 additions & 1 deletion viewing_party/party.py
Original file line number Diff line number Diff line change
@@ -1,23 +1,129 @@
# ------------- WAVE 1 --------------------

def create_movie(title, genre, rating):
pass
if not title or not genre or not rating:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great use of the truthy/falsy values to check for empty strings or None in one statement!

return None
else:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Since it doesn't change the result of our function, I recommend removing the else so we can outdent the main flow of the function.

If an else clause doesn't change the flow of our code, then we have a statement that needs to be compiled and evaluated, but that has no benefit. Additionally, they might cause the main flow of our code to be indented, when we generally want that to be prominent to readers. I generally recommend always questioning if we need an else clause, and only adding one if it's required.

return { "title": title,
"genre": genre,
"rating": rating
}

def add_to_watched(user_data, movie):
user_data["watched"].append(movie)
return user_data

def add_to_watchlist(user_data, movie):
user_data["watchlist"].append(movie)
return user_data

def watch_movie(user_data, title):
for movie in user_data["watchlist"]:
if movie["title"] == title:
add_to_watched(user_data, movie)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice code reuse!

user_data["watchlist"].remove(movie)
break

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great use of break to leave the loop after we make changes!

return user_data


# -----------------------------------------
# ------------- WAVE 2 --------------------
# -----------------------------------------
def get_watched_avg_rating(user_data):
sum = 0.0

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Something to note: the names sum, max, min (and several others) are used by a built in functions of python. We want to avoid using the names of built in functions for our variables because doing so replaces the value of the variable which the built in function was using - meaning we can lose access those built in functions because we no longer have a reference to them.

user_data_len = len(user_data["watched"])
Comment on lines +33 to +34

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

I would consider moving the instantiation of sum to after the validation check. While it holds 0 and we could use it as the return in the validation check, we don't need to take up the extra space of the variable reference if we don't need to accumulate a value in it.

if user_data_len >0:

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

This is a little bit personal taste, but I often prefer to reverse the validation check and use it like a guard clause at the top of the function. This can let us take the main logic of our function out of an if-statement so it is prominent and clear rather than indented. For me, this makes code easier to read, but you may feel differently or work on a team that has guidelines for how they structure validation checks.

We can also use the truthy/falsy value of user_data["watched"] to confirm it is not empty and does not point to None:

if not user_data["watched"]:
    return 0.0

sum = 0.0
for movie in user_data["watched"]:
    sum += movie["rating"]

return sum / len(user_data["watched"])

for movie in user_data["watched"]:
sum += movie["rating"]
Comment on lines +36 to +37

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice approach! Another way we could do this is using list comprehensions and the sum function:

# The list comprehension inside the `sum` call pulls the value of the 
# `rating` key out of each item in the list `user_data['watched']`
sum = sum(movie["rating"] for movie in user_data["watched"])
return sum / len(user_data["watched"])


return sum/user_data_len

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The PEP8 guide on whitespace recommends we always place a single space on either side of an operator. This makes it easier for folks to visually process a line of code and see the groups of related statements in an expression. If you'd like more info, I suggest checking out the style guide for whitespace in expressions and statements: https://peps.python.org/pep-0008/#whitespace-in-expressions-and-statements

else:
return sum


def get_most_watched_genre(user_data):
most_watched_dict = {}
user_data_len = len(user_data["watched"])

if user_data_len > 0:
for movie in user_data["watched"]:
if movie["genre"] in most_watched_dict:
most_watched_dict[movie["genre"]] += 1
else:
most_watched_dict[movie["genre"]] = 0
Comment on lines +49 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There's a tradeoff of using a little more space for another variable, but to make the code a little easier to read and reduce how many times we need to access movie["genre"], I might suggest creating a variable before the if that holds the result of movie["genre"] which we can use on the subsequent lines:

for movie in user_data["watched"]:
    movie_genre = movie["genre"]
    if movie_genre in most_watched_dict:
        most_watched_dict[movie_genre] += 1
    else:
        most_watched_dict[movie_genre] = 1

Comment on lines +49 to +53

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There's an off by one error in how the genres are counted; if we want an accurate count, we would need to start the counter at 1 rather than 0. As an example, let's say we have a list with only one "Action" movie. If we start the count at 0, then most_watched_dict will hold 0 for the key"Action"


most_watched_genre = max(most_watched_dict, key=most_watched_dict.get)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Great use of a frequency map and max!

return most_watched_genre
else:
return None

# -----------------------------------------
# ------------- WAVE 3 --------------------
# -----------------------------------------
def get_unique_watched(user_data):
unique_movies = []
friends_movies = set()

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice use of set!


for friend in user_data["friends"]:
for movie in friend["watched"]:
friends_movies.add(movie["title"])

for user_movie in user_data["watched"]:
if user_movie["title"] not in friends_movies:
unique_movies.append(user_movie)

return unique_movies

def get_friends_unique_watched(user_data):
unique_movies = []
user_movies = []

for user_movie in user_data["watched"]:
user_movies.append(user_movie["title"])
Comment on lines +79 to +82

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

If we don't need complicated logic to transform data, a common way to fill a list in Python is using a list comprehension:

user_movies = [movie["title"] for movie in user_data["watched"]]

Here's a resource on list comprehensions if you want more info: https://realpython.com/list-comprehension-python/


for friend in user_data["friends"]:
for movie in friend["watched"]:
if movie["title"] not in user_movies and movie not in unique_movies:
unique_movies.append(movie)

return unique_movies
Comment on lines +78 to +89

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

There is nothing wrong with this organization, but I want to suggest a slight change that can help readers, especially in longer functions. Instead of declaring all our variables at the top of a function, I recommend creating them just before you need to use them. This help reduce needing to jump around the function or scroll up the page to remind ourselves of what a variable holds.

user_movies = []
for user_movie in user_data["watched"]:
    user_movies.append(user_movie["title"])

unique_movies = []
for friend in user_data["friends"]:
...


# -----------------------------------------
# ------------- WAVE 4 --------------------
# -----------------------------------------

def get_available_recs(user_data):
friends_recs = []
friends_unique_data = get_friends_unique_watched(user_data)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice code reuse!


if len(friends_unique_data) >0 and len(user_data["watched"])> 0:
for movie in friends_unique_data:
if movie["host"] in user_data["subscriptions"]:
friends_recs.append(movie)
Comment on lines +100 to +102

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nice algorithm! Another way we could approach filtering the unique_watched list is with a list comprehension:

# list comprehension
result = [movie for movie in friends_unique_data if movie["host"] in user_data["subscriptions"]]

This line is a bit long, in practice we would split a statement like this across lines, use some extra variables, or shorten some naming to keep under the PEP8 guide of 79 characters max per line:

# list comprehension
result = [movie for movie in friends_unique_data 
         if movie["host"] in user_data["subscriptions"]]


return friends_recs


# -----------------------------------------
# ------------- WAVE 5 --------------------
# -----------------------------------------

def get_new_rec_by_genre(user_data):
rec_by_genre = []
most_watched_genre = get_most_watched_genre(user_data)
recs = get_available_recs(user_data)
for movie in recs:
if movie["genre"] == most_watched_genre:
rec_by_genre.append(movie)

return rec_by_genre
Comment on lines +112 to +119

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Though this solution passes the current test suite, it isn't doing what the problem statement asks.

The test test_new_genre_rec uses clean_wave_5_data() as the input, which includes subscription data. If we remove "hulu" from the user's subscriptions on line 157 of test_constants.py, suddenly the test test_new_genre_rec starts failing. Our problem statement for get_new_rec_by_genre does not ask us to consider subscriptions though, so changing that subscription data should not change the result of get_new_rec_by_genre.

  • What steps can you take to diagnose what is happening?
  • What was the cause of the issue?


def get_rec_from_favorites(user_data):
rec_from_favorites = []
unique_watched = get_unique_watched(user_data)

if len(user_data["favorites"]) >0:
for movie in unique_watched:
if movie in user_data["favorites"]:
rec_from_favorites.append(movie)
return rec_from_favorites