Skip to content

Snow Leopards- Ja Hopkins #105

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 20 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
856770c
define add and remove instance methods for vendor class
lavazane Oct 18, 2022
878a4b1
fix spelling
lavazane Oct 18, 2022
18baf51
add assertion statement in wave 1 test
lavazane Oct 18, 2022
52f2a3c
create item class and define constructor
lavazane Oct 18, 2022
897a389
remove import
lavazane Oct 19, 2022
9c74236
define get_by_category instance method in Vendor class
lavazane Oct 19, 2022
042f6f4
add assertion statements to test_wave_02
lavazane Oct 19, 2022
a65eb5c
def __str__ instance method for Item Class
lavazane Oct 19, 2022
826c303
previous commit was actually to run wave 03 test. Def __str__ instanc…
lavazane Oct 19, 2022
0608a44
updated category to be assined to an empty string instead of an empty…
lavazane Oct 19, 2022
6ed61d0
created clothing class as a child class to Item
lavazane Oct 19, 2022
c2d2b26
def swap_first_item instance method for Vendor class
lavazane Oct 19, 2022
a62ccca
create electronic child class
lavazane Oct 19, 2022
56fe85a
final commit before review
lavazane Oct 20, 2022
62fa0bf
dry up code in vendor class by removing unneccsary Item instance
lavazane Oct 21, 2022
9e1d477
finish creating electronic and decor classes. test case wouldn't work…
lavazane Oct 21, 2022
0fead23
def condition descripttion instance method in Item class
lavazane Oct 21, 2022
fa98af6
reconstrut Vendor class, change default parameter to None from empty …
lavazane Oct 21, 2022
39e96c1
removed dictionary
lavazane Oct 21, 2022
9d0e08b
ran unit and integration tests
lavazane Oct 21, 2022
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
12 changes: 10 additions & 2 deletions swap_meet/clothing.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,10 @@
class Clothing:
pass
from swap_meet.item import Item

Choose a reason for hiding this comment

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

👍 Since we are inheriting from Item, which requires us to get the Item name from the item module, we need to import this.


class Clothing(Item):

def __init__(self, condition = 0):

Choose a reason for hiding this comment

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

Nice job removing category from the available initializer parameters. We don't want the caller to be able to set a different category. Though, the caller could always change the category by assigning directly to it after. But at least we're indicating that they shouldn't do that.

Choose a reason for hiding this comment

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

Typically, for default values we leave the spaces off around the =

    def __init__(self, condition=0):

super().__init__(category = "Clothing", condition = condition)

Choose a reason for hiding this comment

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

👍 Great use of Item's initializer as a helper, to complete the setup of the child type.

Choose a reason for hiding this comment

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

When calling methods with keyword arguments, we tend to avoid using spaces around the =

        super().__init__(category="Clothing", condition=condition)



def __str__(self):

Choose a reason for hiding this comment

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

👍

return ("The finest clothing you could wear.")
11 changes: 9 additions & 2 deletions swap_meet/decor.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
class Decor:
pass
from swap_meet.item import Item

class Decor(Item):

Choose a reason for hiding this comment

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

👍


def __init__(self, condition = 0):
super().__init__(category = "Decor", condition = condition)

def __str__(self):
return ("Something to decorate your space.")
11 changes: 9 additions & 2 deletions swap_meet/electronics.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
class Electronics:
pass
from swap_meet.item import Item

class Electronics(Item):

Choose a reason for hiding this comment

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

👍


def __init__(self, condition = 0):
super().__init__(category = "Electronics", condition = condition)

def __str__(self):
return ("A gadget full of buttons and secrets.")
22 changes: 21 additions & 1 deletion swap_meet/item.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,22 @@
class Item:
pass

def __init__(self, category= "", condition = 0):

Choose a reason for hiding this comment

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

👍

self.category = category
self.condition = condition

def __str__(self):

Choose a reason for hiding this comment

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

👍 This being here mostly just proves that we can replace an implementation in child classes.

return ("Hello World!")


# conditional statements

def condition_description(self):
if self.condition == 5:
description = "Perfect"
elif 0 <= self.condition <= 2:
description = "Used with minor flaws"
elif 2.1 <= self.condition <= 3:
description = "Used with little to no flaws"
elif 3.1 <= self.condition <= 4.9:
description = "Like New"
return description
Comment on lines +13 to +22

Choose a reason for hiding this comment

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

Notice that the only lines not covered by tests are related to this function. Due to the explicit logic checks, we would need to add additional test cases to ensure that each of the possible descriptions is returned for each possible condition, since each condition could potentially have a bug in it. Explicitly writing the logic does give us the ultimate control over exactly under which situations a particular description should be used, but especially when we have a long list of chained checks that are very similar, we could try coming up with a way to represent it with data and use iteration. A basic example of this that would closely mirror your existing behavior (the ranges aren't exactly matched, and the existing implementation has a few gaps, such as at 3.05) could look like:

# outside class definition
DESCRIPTIONS = (
    "Used with minor flaws",
    "Used with minor flaws",
    "Used with minor flaws",
    "Used with little to no flaws",
    "Like New",
    "Perfect",
)

from math import ceil

# in Item class definition
    def condition_description(self):
        return DESCRIPTIONS[ceil(self.condition)]

We would still want to think about the edge case behavior (what if the condition is less than 0 or greater than 5), but we would have more general confidence that if this logic works for a particular condition, it's likely to work for all of them.

68 changes: 67 additions & 1 deletion swap_meet/vendor.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,68 @@
from swap_meet.item import Item

Choose a reason for hiding this comment

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

👀 We don't need to import Item into this file since we never use it by name. Check out the Why Vendor Doesn't Need to Import Item for more about why.

class Vendor:
pass

def __init__(self, inventory=None):
# self.inventory = inventory
if inventory is None:

Choose a reason for hiding this comment

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

👍 Nice handling of the default inventory case.

inventory = []
self.inventory = inventory

def add(self, item):
self.item = item

Choose a reason for hiding this comment

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

👀 We don't need to store a reference to the item in the Vendor directly. Adding it to the inventory is sufficient.

And in general, we should avoid assigning to self attributes that aren't initialized in the constructor, as this effectively changes the attributes that are available depending on which methods have been called, which can be confusing. There are linting tools (tools to report code issues that aren't necessarily errors, but are somewhat suspect) that will warn about assigning to a new attribute like this.

self.inventory.append(item)
return item

Choose a reason for hiding this comment

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

👀 Leave a blank line between methods.

def remove(self, item):
self.item = item

Choose a reason for hiding this comment

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

👀 No need to store the item reference here either.

if item in self.inventory:
self.inventory.remove(item)
return item
return False
Comment on lines +16 to +19

Choose a reason for hiding this comment

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

Prefer writing the error handling case as a guard clause first, which lets us then emphasize the main part of the code by not indenting it.

        if item not in self.inventory:
            return False

        self.inventory.remove(item)
        return item

We could also do this with a more EAFP approach by using remove or index directly and handling the error that would get raised if the item were not present (ValueError).


# create get_by_cateogry instance method that takes in category argument

def get_by_category(self, category):
# # reutrn list of items by category
self.category = category

Choose a reason for hiding this comment

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

👀 Like the self.item cases above, this is not needed and should be avoided.

items_in_category = []
# instance from item class
# item_category = Item(category=self.category)
for item in self.inventory:
# item_category = Item(category=self.category)
# if item.category == item_category.category:
if item.category == category:
items_in_category.append(item)
Comment on lines +29 to +33

Choose a reason for hiding this comment

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

This would be a great opportunity for a comprehension expression. We could write this as

    items_in_category = [item for item in self.inventory if item.category == category]

return items_in_category

def swap_items(self, vendor, my_item, their_item):
self.vendor = vendor
self.my_item = my_item
self.their_item = their_item
Comment on lines +37 to +39

Choose a reason for hiding this comment

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

👀 Like the self.item cases above, these assignments are not needed and should be avoided.

my_items = Vendor(self.inventory)
if my_item not in my_items.inventory or their_item not in vendor.inventory:
return False
my_items.remove(my_item)
my_items.add(their_item)
vendor.add(my_item)
vendor.remove(their_item)
Comment on lines +40 to +46

Choose a reason for hiding this comment

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

Rather than building a new Vendor (which happens to share the same inventory list as the current Vendor), we can use the self instance directly.

        if my_item not in self.inventory or their_item not in vendor.inventory:
            return False

        self.remove(my_item)
        self.add(their_item)
        vendor.add(my_item)
        vendor.remove(their_item)

return True

def swap_first_item(self, vendor):
self.vendor = vendor
if not self.inventory or not vendor.inventory:
return False
Comment on lines +51 to +52

Choose a reason for hiding this comment

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

👍 Here, we do need to ensure that the lists aren't empty before trying to access index 0.

And consider adding a blank line at the end of the condition block to help visually separate the the guard clause from the main logic.

return self.swap_items(vendor, self.inventory[0], vendor.inventory[0])

Choose a reason for hiding this comment

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

👍 Nice function reuse. Though notice that due to the remove calls in swap_items, this will be O(n) where n is the lengths of the inventories, since we'll have to shift the whole remaining part of the inventories forward.

By taking a different approach with swap_items or customizing our implementation here, we could do this swap in constant time.




# items_in_category = []
# for i in self.inventory:
# if i == Item(category=category):
# items_in_category.append(i)

# return items_in_category


# Item.category



4 changes: 2 additions & 2 deletions tests/integration_tests/test_wave_01_02_03.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from swap_meet.vendor import Vendor
from swap_meet.item import Item

@pytest.mark.skip
@pytest.mark.integration_test
# @pytest.mark.skip
# @pytest.mark.integration_test

Choose a reason for hiding this comment

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

The integration test decorator should not have been commented out. This mark affects the order that tests are run, as well as how the code coverage is calculated.

def test_integration_wave_01_02_03():
# make a vendor
vendor = Vendor()
Expand Down
13 changes: 7 additions & 6 deletions tests/unit_tests/test_wave_01.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
import pytest
from swap_meet.vendor import Vendor

@pytest.mark.skip
# @pytest.mark.skip
def test_vendor_has_inventory():
vendor = Vendor()
assert len(vendor.inventory) == 0

@pytest.mark.skip
# @pytest.mark.skip
def test_vendor_takes_optional_inventory():
inventory = ["a", "b", "c"]
vendor = Vendor(inventory=inventory)
Expand All @@ -16,7 +16,7 @@ def test_vendor_takes_optional_inventory():
assert "b" in vendor.inventory
assert "c" in vendor.inventory

@pytest.mark.skip
# @pytest.mark.skip
def test_adding_to_inventory():
vendor = Vendor()
item = "new item"
Expand All @@ -27,7 +27,7 @@ def test_adding_to_inventory():
assert item in vendor.inventory
assert result == item

@pytest.mark.skip
# @pytest.mark.skip
def test_removing_from_inventory_returns_item():
item = "item to remove"
vendor = Vendor(
Expand All @@ -40,7 +40,7 @@ def test_removing_from_inventory_returns_item():
assert item not in vendor.inventory
assert result == item

@pytest.mark.skip
# @pytest.mark.skip
def test_removing_not_found_is_false():
item = "item to remove"
vendor = Vendor(
Expand All @@ -49,7 +49,8 @@ def test_removing_not_found_is_false():

result = vendor.remove(item)

raise Exception("Complete this test according to comments below.")
# raise Exception("Complete this test according to comments below.")
# *********************************************************************
# ****** Complete Assert Portion of this test **********
# *********************************************************************
assert result == False

Choose a reason for hiding this comment

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

👍 While we wouldn't typically compare directly with False in Python code, if we want to confirm that the result is False, as required in the description (rather than just something falsy) then we can write this. We also may want to ensure that the length of the vendor's inventory was unchanged by the operation.

Personally, I don't like the design the description asks for (I'd rather it either return None or raise an error), but 🤷 .

14 changes: 10 additions & 4 deletions tests/unit_tests/test_wave_02.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,12 @@
from swap_meet.vendor import Vendor
from swap_meet.item import Item

@pytest.mark.skip
# @pytest.mark.skip
def test_items_have_blank_default_category():
item = Item()
assert item.category == ""

@pytest.mark.skip
# @pytest.mark.skip
def test_get_items_by_category():
item_a = Item(category="clothing")
item_b = Item(category="electronics")
Expand All @@ -23,7 +23,7 @@ def test_get_items_by_category():
assert item_c in items
assert item_b not in items

@pytest.mark.skip
# @pytest.mark.skip
def test_get_no_matching_items_by_category():
item_a = Item(category="clothing")
item_b = Item(category="clothing")
Expand All @@ -34,7 +34,13 @@ def test_get_no_matching_items_by_category():

items = vendor.get_by_category("electronics")

raise Exception("Complete this test according to comments below.")
# raise Exception("Complete this test according to comments below.")

assert items == []
assert len(items) == 0
assert item_a not in items
assert item_b not in items
assert item_c not in items
Comment on lines +39 to +43

Choose a reason for hiding this comment

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

👍 The comparison against the empty list is really all we need. If the list is empty, none of those items can possibly be in it.

As before, we usually don't explicitly check for an empty collection like this, but since the unit test is confirming the specified behavior, which was to return a list (not just something falsy), we can write it this way.

# *********************************************************************
# ****** Complete Assert Portion of this test **********
# *********************************************************************
12 changes: 6 additions & 6 deletions tests/unit_tests/test_wave_03.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@
from swap_meet.vendor import Vendor
from swap_meet.item import Item

@pytest.mark.skip
# @pytest.mark.skip
def test_item_overrides_to_string():
item = Item()

stringified_item = str(item)

assert stringified_item == "Hello World!"

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_items_returns_true():
item_a = Item(category="clothing")
item_b = Item(category="clothing")
Expand Down Expand Up @@ -38,7 +38,7 @@ def test_swap_items_returns_true():
assert item_b in jolie.inventory
assert result

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_items_when_my_item_is_missing_returns_false():
item_a = Item(category="clothing")
item_b = Item(category="clothing")
Expand All @@ -65,7 +65,7 @@ def test_swap_items_when_my_item_is_missing_returns_false():
assert item_e in jolie.inventory
assert not result

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_items_when_their_item_is_missing_returns_false():
item_a = Item(category="clothing")
item_b = Item(category="clothing")
Expand All @@ -92,7 +92,7 @@ def test_swap_items_when_their_item_is_missing_returns_false():
assert item_e in jolie.inventory
assert not result

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_items_from_my_empty_returns_false():
fatimah = Vendor(
inventory=[]
Expand All @@ -112,7 +112,7 @@ def test_swap_items_from_my_empty_returns_false():
assert len(jolie.inventory) == 2
assert not result

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_items_from_their_empty_returns_false():
item_a = Item(category="clothing")
item_b = Item(category="clothing")
Expand Down
6 changes: 3 additions & 3 deletions tests/unit_tests/test_wave_04.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from swap_meet.vendor import Vendor
from swap_meet.item import Item

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_first_item_returns_true():
item_a = Item(category="clothing")
item_b = Item(category="clothing")
Expand Down Expand Up @@ -30,7 +30,7 @@ def test_swap_first_item_returns_true():
assert item_a in jolie.inventory
assert result

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_first_item_from_my_empty_returns_false():
fatimah = Vendor(
inventory=[]
Expand All @@ -48,7 +48,7 @@ def test_swap_first_item_from_my_empty_returns_false():
assert len(jolie.inventory) == 2
assert not result

@pytest.mark.skip
# @pytest.mark.skip
def test_swap_first_item_from_their_empty_returns_false():
item_a = Item(category="clothing")
item_b = Item(category="clothing")
Expand Down
10 changes: 5 additions & 5 deletions tests/unit_tests/test_wave_05.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,25 +3,25 @@
from swap_meet.decor import Decor
from swap_meet.electronics import Electronics

@pytest.mark.skip
# @pytest.mark.skip
def test_clothing_has_default_category_and_to_str():
cloth = Clothing()
assert cloth.category == "Clothing"
assert str(cloth) == "The finest clothing you could wear."

@pytest.mark.skip
# @pytest.mark.skip
def test_decor_has_default_category_and_to_str():
decor = Decor()
assert decor.category == "Decor"
assert str(decor) == "Something to decorate your space."

@pytest.mark.skip
# @pytest.mark.skip
def test_electronics_has_default_category_and_to_str():
electronics = Electronics()
assert electronics.category == "Electronics"
assert str(electronics) == "A gadget full of buttons and secrets."

@pytest.mark.skip
# @pytest.mark.skip
def test_items_have_condition_as_float():
items = [
Clothing(condition=3.5),
Expand All @@ -31,7 +31,7 @@ def test_items_have_condition_as_float():
for item in items:
assert item.condition == pytest.approx(3.5)

@pytest.mark.skip
# @pytest.mark.skip
def test_items_have_condition_descriptions_that_are_the_same_regardless_of_type():
items = [
Clothing(condition=5),
Expand Down