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

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 work importing Item since we're using by name as the parent class of Clothing 👍


class Clothing(Item):
def __init__(self, id=None, fabric="Unknown", condition=0):
super().__init__(id=id, condition=condition)
Comment on lines +4 to +5

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 work on here!

On line 4 you correctly set the default value for fabric to "Unknown"

On line 5 you properly call the initializer of the parent, and pass along the values the child wants to use.

self.fabric = fabric

def __str__(self):
return f"An object of type Clothing with id {self.id}. It is made from {self.fabric} fabric."

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 method looks similar to the method you wrote in the Item parent class.

In that class __str__ returns f"An object of type Item with id {self.id}."

What if the __str__ method for the Clothing class could inherit this behavior from the parent class and then append the sentence with the second part of the fabric message "It is made from {self.fabric} fabric." ?

Currently, it's not possible because Item's __str__ method hardcodes the classtype "Item" in the string it returns. However, you could refactor the method to make it more dynamic so that it will print any class's name.

After Item's __str__ method is refactored, then Clothing's method could override it like this:

    def __str__(self):
        type_str = super().__str__()
        fabric_str = f"It is made from {self.fabric} fabric."
        return " ".join([type_str, fabric_str])


13 changes: 11 additions & 2 deletions swap_meet/decor.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,11 @@
class Decor:
pass
from swap_meet.item import Item

class Decor(Item):
def __init__(self, id=None, width=0, length=0, condition=0):
super().__init__(id=id, condition=condition)
self.width = width
self.length = length

def __str__(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

See comment from clothing.py about refactoring this method so it overrides the parent class's __str__ method.

return f"An object of type Decor with id {self.id}. It takes up a {self.width} by {self.length} sized 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):
def __init__(self, id=None, type="Unknown", condition=0):
super().__init__(id=id, condition=condition)
self.type = type

def __str__(self):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prefer to override parent class's implementation of __str__ like my comment above describes.

return f"An object of type Electronics with id {self.id}. This is a {self.type} device."
35 changes: 34 additions & 1 deletion swap_meet/item.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,35 @@
import uuid

class Item:
pass
def __init__(self,id=None, condition=0, age=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.

Suggested change
def __init__(self,id=None, condition=0, age=0):
def __init__(self, id=None, condition=0, age=0):

Nit: You're missing a whitespace after the first comma. Be mindful to use/exclude whitespaces where necessary. It is easy to overlook, but having inconsistent use of whitespaces leads to a messy codebase.

Here's the section on whitespaces in the PEP8 styleguide.

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 work tackling the optional enhancements relating to an item's age. You set a default param age equal to 0 here. However, have a look at Item's subclasses?

Is there a way to pass in an age argument to subclass constructors? We need a way to set the age of an subclass when we create an instance of it.

As Clothing, Decor, Electronics are defined right now, I couldn't write something like:

item_clothing1 = Clothing(condition=1.0, id=123, fabric="Geometric Pattern", age=3)

because I'd get an error that I supplied too many arguments to the constructor.

How would you update the subclasses so that age can be set?

self.id = id if id is not None else uuid.uuid4().int
self.condition = condition
self.age = age

def get_category(self):
return self.__class__.__name__
Comment on lines +9 to +10

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 job writing this method in the super class and not repeating the logic in the sub classes 👍

# The method get_category() defined in the parent class already works correctly for child classes
# because self.__class__.__name__ reflects the class of the instance, not the class where the method is defined.
# Python dynamically resolves self.__class__.__name__ at runtime,
# so it always returns the correct class name for the current instance, regardless of where the method is defined.


def __str__(self):
return f"An object of type Item with id {self.id}."
Comment on lines +17 to +18

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 mentioned in the children classes of Item that they could override this __str__ class here.

How can you use string interpolation here so that the word "Item" is not hardcoded?

How can you make use of the instance method get_category that you have written on lines 9-10 here since it returns the class name of the current instance?


def condition_description(self):
if self.condition == 0:
return "Broken"
elif self.condition == 1:
return "Poor"
elif self.condition == 2:
return "Fair"
elif self.condition == 3:
return "Good"
elif self.condition == 4:
return "Like New"
elif self.condition == 5:
return "New, In Box"
Comment on lines +21 to +32

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 implementation works! It is a little repetitive, and could be considered brittle (prone to breaking) since the data relationships are encoded here (mapping a number to a string).

What data structures do you know of that could capture such an associative relationship like this? How about a dictionary with its key/value pairs?

ITEM_CONDITIONS = {
    0: "Broken", 
    1: "Poor", 
    2: "Fair", ... 
}

return ITEMS_CONDITIONS.get(self.condition)

Having a clear representation of the data can make code easier to read and maintain than complex conditionals.




117 changes: 116 additions & 1 deletion swap_meet/vendor.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,117 @@
class Vendor:
pass
def __init__(self, inventory=None,):

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Nit: remove trailing comma after None

inventory = [] if inventory is None else inventory
self.inventory = inventory
Comment on lines +3 to +4

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 work using a ternary to handle the default inventory case.

You can combine lines 3-4 together:

self.inventory = [] if inventory is None else inventory


def add(self, item):
self.inventory.append(item)
return item

def remove(self, item):
if item not in self.inventory:
return False
else:
self.inventory.remove(item)
return item
Comment on lines +10 to +15

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 you've reversed the logic to put the error checking first as a "guard clause" this allows us to unindent the main logic and remove else.

if item not in self.inventory: 
    return False 

self.inventory.remove(item) 
return item


def get_by_id(self, id):
for item in self.inventory:
if item.id == id:
return item

return None

def swap_items(self, other_vendor, my_item, their_item):
if my_item in self.inventory and their_item in other_vendor.inventory:
self.inventory.remove(my_item)
self.inventory.append(their_item)
other_vendor.inventory.append(my_item)
other_vendor.inventory.remove(their_item)
Comment on lines +26 to +29

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Removing and appending elements to a list works! If you haven't seen it already, Python also has syntax for assigning multiple values in one line that we can leverage to swap elements in a list (read more here)

For example:

a, b = 100, 200

print(a)
# 100

print(b)
# 200

We can use that syntax to swap items like so:

my_item_index = self.inventory.index(my_item)
their_item_index = other_vendor.inventory.index(their_item)
other_vendor.inventory[their_item_index], self.inventory[my_item_index] = my_item, their_item

return True
else:
return False
Comment on lines +31 to +32

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 the logic that swaps items and returns True will only execute if the expression on line 25 evaluates to True we can remove else and unindent line 32.

Prefer to have fewer levels of nested logic where possible.

Alternatively, the approach I prefer is to use a guard clause to first check if the items are not in their respective lists and return False. When you invert the logic with a guard clause then your main logic for this method (lines 26-30) can be unindented. This emphasizes that the unindented logic outside of the guard clause is the main thing.

def swap_items(self, other_vendor, my_item, their_item):
        if my_item not in self.inventory and their_item not in other_vendor.inventory:
                return False

        self.inventory.remove(my_item)
        self.inventory.append(their_item)
        other_vendor.inventory.append(my_item)
        other_vendor.inventory.remove(their_item)
        return True
        


def swap_first_item(self, other_vendor):
# original way:
# if not self.inventory or not other_vendor.inventory:
# return False
# else:
# other_vendor.inventory.append(self.inventory[0])
# self.inventory.remove(self.inventory[0])
# self.inventory.append(other_vendor.inventory[0])
# other_vendor.inventory.remove(other_vendor.inventory[0])
# return True

if not self.inventory or not other_vendor.inventory:
return False
Comment on lines +45 to +46

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 that the inventories aren't empty, which would cause the [0] accesses to fail.

else:
my_first_item = self.inventory[0]
their_first_item = other_vendor.inventory[0]
self.swap_items(other_vendor, my_first_item, their_first_item)
return True
Comment on lines +47 to +51

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 you use the guard clause pattern on lines 45-46, we should remove else and unindent the main logic here on lines 48-51.

Good work using the swap_items method you already implemented to keep your code DRY.


def get_by_category(self, category):
category_list = []

for item in self.inventory:
if category == item.get_category():
category_list.append(item)

return category_list
Comment on lines +54 to +60

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Instead of creating an empty list and appending to it in a for-loop, how could you use list comprehension instead?


def get_best_by_category(self, category):
best_item = self.inventory[0]

for item in self.inventory:
if category == item.get_category() and item.condition > best_item.condition:
Comment on lines +65 to +66

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

How could you simplify this logic so that you loop over a list of items belonging to the category that is passed to this method (instead of looping over every item in self.inventory? How about invoking get_by_category that you already implemented?

best_item = item

if best_item.get_category() == category:

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 you start out with only a list of items belonging to the specified category, then we can get rid of this additional check on line 69 too and further reduce the complexity of this method.

Ultimately, get_best_by_category could be refactored to:

def get_best_by_category(self, category):
    category_items = self.get_by_category(category)

    if not category_items:
            return None

    highest_item = None
    highest_rating = 0
    for item in item_list:
        if item.condition > highest_rating:
        highest_item = item
        highest_rating = item.condition
    return highest_item

return best_item
else:
return None


def swap_best_by_category(self, other_vendor, my_priority, their_priority):
if not other_vendor.inventory or not self.inventory:
return False

my_item_to_swap = self.get_best_by_category(their_priority)
their_item_to_swap = other_vendor.get_best_by_category(my_priority)

if my_item_to_swap and their_item_to_swap:
self.swap_items(other_vendor, my_item_to_swap, their_item_to_swap)
return True
elif not my_item_to_swap or not their_item_to_swap:
return False
Comment on lines +82 to +86

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Prefer to have fewest levels of indentation and remove elif to emphasize that the logic on lines 83-84 is the main part of this method. As you continue coding and implementing solutions with complex logic, ask yourself "What is the main logic and how can I make that evident without nesting the logic in conditional statements?"

Lines 82-86 can be refactored to look like this:

if not my_item_to_swap or not their_item_to_swap:
    return False

self.swap_items(other_vendor, my_item_to_swap, their_item_to_swap)
return True


# Optional
# get the newest item and then swap the item
# situation haven't consider:
# 1. if there're more thean one item with same age?
# 2. if my inventory or their inventory is empty

def get_newest_item(self):
newest_item = self.inventory[0]

for item in self.inventory:
if item.age < newest_item.age:
newest_item = item

return newest_item

def swap_by_newest(self, other_vendor):
my_newest_item = self.get_newest_item()
their_newest_item = other_vendor.get_newest_item()

return self.swap_items(other_vendor, my_newest_item, their_newest_item)
Comment on lines +94 to +107

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

👍 LGTM!










Comment on lines +108 to +116

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Only need one blank line after the end of your code in a file. You can remove the excess blank lines to keep your project neat.


2 changes: 1 addition & 1 deletion tests/integration_tests/test_wave_01_02_03.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
@pytest.mark.integration_test
def test_integration_wave_01_02_03():
# make a vendor
Expand Down
2 changes: 1 addition & 1 deletion tests/integration_tests/test_wave_04_05_06.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
from swap_meet.decor import Decor
from swap_meet.electronics import Electronics

@pytest.mark.skip
# @pytest.mark.skip
@pytest.mark.integration_test
def test_integration_wave_04_05_06():
camila = Vendor()
Expand Down
30 changes: 30 additions & 0 deletions tests/unit_tests/test_swap_by_newest.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import pytest
from swap_meet.item import Item
from swap_meet.vendor import Vendor

def test_swap_by_newest():
item_a = Item(age=2)
item_b = Item(age=4)
item_c = Item(age=3)
fatimah = Vendor(
inventory=[item_a, item_b, item_c]
)

item_d = Item(age=1)
item_e = Item(age=4)
jolie = Vendor(
inventory=[item_d, item_e]
)

result = fatimah.swap_by_newest(jolie)

assert len(fatimah.inventory) == 3
assert item_b in fatimah.inventory
assert item_a not in fatimah.inventory
assert item_c in fatimah.inventory
assert item_d in fatimah.inventory
assert len(jolie.inventory) == 2
assert item_d not in jolie.inventory
assert item_e in jolie.inventory
assert item_a in jolie.inventory
assert result
Comment on lines +21 to +30

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

LGTM! Gad you got some additional practice with this optional enhancement!

15 changes: 6 additions & 9 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,4 @@ def test_removing_not_found_is_false():

result = vendor.remove(item)

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

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 the general intention of this method is to modify the inventory, we want to check this condition as well to make sure that the items that started in the inventory are still there. Adding these assertions would make the test more robust.

    assert len(vendor.inventory) == 3
    assert "a" in vendor.inventory
    assert "b" in vendor.inventory
    assert "c" in vendor.inventory

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

@pytest.mark.skip
# @pytest.mark.skip
def test_items_have_default_uuid_length_id():
item = Item()
assert isinstance(item.id, int)
assert len(str(item.id)) >= 32

@pytest.mark.skip
# @pytest.mark.skip
def test_item_instances_have_different_default_ids():
item_a = Item()
item_b = Item()
assert item_a.id != item_b.id

@pytest.mark.skip
# @pytest.mark.skip
def test_items_use_custom_id_if_passed():
item = Item(id=12345)
assert isinstance(item.id, int)
assert item.id == 12345

@pytest.mark.skip
# @pytest.mark.skip
def test_item_obj_returns_text_item_for_category():
item = Item()
assert item.get_category() == "Item"

@pytest.mark.skip
# @pytest.mark.skip
def test_get_item_by_id():
test_id = 12345
item_custom_id = Item(id=test_id)
Expand All @@ -36,7 +36,7 @@ def test_get_item_by_id():
result_item = vendor.get_by_id(test_id)
assert result_item is item_custom_id

@pytest.mark.skip
# @pytest.mark.skip
def test_get_item_by_id_no_matching():
test_id = 12345
item_a = Item()
Expand Down
Loading