def do_post_create_item(self): post_data = get_raw_post_data(self) item_dict = parse_json(post_data) # Pull out all of the fields we need identifier = get_value(item_dict, 'identifier') price = parse_float(get_value(item_dict, 'price', 0)) billing_method = get_value(item_dict, 'billing_method', '') special = get_value(item_dict, 'special') # Ensure that all necessary data is present msg = '' if identifier is None or identifier == '': msg += 'Must provide identifier. ' if price is None or price <= 0: msg += 'Must provide price and must be positive. ' if msg != '': set_response(self, 400, msg, 'text/text') else: # Check to see if the provided special is valid if special is not None: msg = validate_special(special, billing_method) if msg != '': set_response(self, 400, msg, 'text/text') else: # Create and store the item and tell the user everything is fine item = Item(identifier, price, billing_method, special) datastore.set('itemdetails:' + item.identifier, item) set_response(self, 200, '')
def calculate_best_savings(self, applied_to_item, items, datastore): item = datastore.get('itemdetails:' + get_value(applied_to_item, 'identifier')) # How many items are involved in each application of this special chunk_size = self.quantity # If the number of items you have to buy is greater than the limit, # there is no way this special can apply to anything if self.limit is not None and chunk_size > self.limit: return (0, []) quantity = get_value(applied_to_item, 'quantity', 0) # If the customer hasn't bought the minimum quantity, there is no # way there can be any savings if quantity < chunk_size: return (0, []) # If the quantity the customer is buying is more than the limit for # the special, just decrease the quantity that the special will be # applied to if self.limit is not None and quantity > self.limit: quantity = self.limit # How many applications of this special can there be? chunks = math.floor(quantity / chunk_size) # Return the original price minus the price with discounts savings = (chunks * chunk_size * item.price) - (chunks * self.price) return (round(savings, 2), [{ 'identifier': item.identifier, 'quantity': chunks * chunk_size }])
def do_post_add_item_to_order(self): post_data = get_raw_post_data(self) post_dict = parse_json(post_data) order_id = get_value(post_dict, 'order_id') item_identifier = get_value(post_dict, 'item_identifier') msg = '' if order_id is None or order_id == '': msg += 'Must provide order_id. ' if item_identifier is None: msg += 'Must provide item. ' if msg != '': set_response(self, 400, msg, 'text/text') else: order = datastore.get('orders:' + order_id) item = datastore.get('itemdetails:' + item_identifier) if order is None: set_response(self, 400, 'Order does not exist.', 'text/text') elif item is None: set_response(self, 400, 'Item does not exist.', 'text/text') else: # If the item is a UNIT type, we only want to allow integers # for the quantity since it doesn't make sense to have something # like 1.25 cans of soup quantity = parse_float(get_value(post_dict, 'quantity'), 1.0) if item.billing_method == Methods.UNIT: quantity = parse_int(quantity) order.add_item(item, quantity) set_response(self, 200, '')
def do_post_data_store(self): post_data = get_raw_post_data(self) post_dict = parse_json(post_data) key = get_value(post_dict, 'key') value = get_value(post_dict, 'value') if key is None: set_response(self, 400, 'Must provide key', 'text/text') elif value is None: set_response(self, 400, 'Must provide value', 'text/text') else: datastore.set(key, value) set_response(self, 200, '')
def calculate_total_with_specials_greedy(self): savings = 0.00 # Create a copy of self.items so we don't have to worry about # modifying it items_copy = copy.deepcopy(self.items) # Want to continue this greedy algorithm until there are no more # specials left to yield savings while has_specials(items_copy, datastore): best_special_savings = 0.00 best_special_consumed = [] # Find the special that will save the customer the most money for item, quantity in items_copy.items(): item_def = datastore.get('itemdetails:' + item) if item_def.special is not None: (savings, items_consumed ) = item_def.special.calculate_best_savings( { 'identifier': item, 'quantity': quantity }, items_copy, datastore) # Check to see if the current special would give the customer # the greatest savings if savings > best_special_savings: best_special_savings = savings best_special_consumed = items_consumed # If there was no special that saved the customer money, then # there are no more specials that can be applied and we are done if best_special_savings == 0.00: break # Remove all items that were consumed by the best special for item_consumed in best_special_consumed: consumed_name = get_value(item_consumed, 'identifier') items_copy[consumed_name] -= get_value( item_consumed, 'quantity') # If all of this item has been consumed by specials, remove it from the # possible items to consume or be used in the future if items_copy[consumed_name] == 0: del items_copy[consumed_name] # Increment the savings by the quantity that the best special saved savings += best_special_savings return round(self.calculate_total_no_specials() - savings, 2)
def test_post_create_item_when_given_no_billing_method_defaults_to_unit(self): post_data = {'identifier': 'cherries', 'price': 1.00} r = requests.post(baseurl + '/createitem', data=json.dumps(post_data)) self.assertEqual(r.status_code, 200) r = requests.get(baseurl + '/getitem?identifier=cherries') item = json.loads(r.text) self.assertEqual(get_value(item, 'billing_method'), 'unit')
def do_get_order(self): url_query = parse_url_query(self.path) order_id = get_value(url_query, 'id') if order_id is None or order_id == '': set_response(self, 400, 'Must provide order id') else: order = datastore.get('orders:' + order_id) if order is None: set_response(self, 400, 'Order does not exist.') else: set_response(self, 200, order.to_json())
def remove_item(self, item, removed_quantity): # Get the quantity we currently are holding for this order current_quantity = get_value(self.items, item.identifier, 0) # Remove the desired quantity new_quantity = current_quantity - removed_quantity # It doesn't make sense to have a zero or negative quantity of items # so if the value goes negative or is 0, we want to delete the item # from the order if new_quantity <= 0: del self.items[item.identifier] else: self.items[item.identifier] = new_quantity
def calculate_best_savings(self, applied_to_item, items, datastore): # Get the item definition and the value that the other item must be # equal to or less than in cost item_def = datastore.get('itemdetails:' + get_value(applied_to_item, 'identifier')) max_cost = get_value(applied_to_item, 'quantity') * item_def.price # Find the highest cost item that is less than or equal to the max_cost best_cost = 0 best_item = None for item, quantity in items.items(): # Skip the item we're applying the special to if item == item_def.identifier: continue # Get the definition for the item we're currently looking at current_item_def = datastore.get('itemdetails:' + item) # Determine if it's better than any previous item we've seen # We can allow weighted items, but if we do the special must consume the # entire weight if current_item_def.billing_method.value == 'weight': cost = current_item_def.price * quantity if current_item_def.price * quantity <= max_cost and cost > best_cost: best_cost = cost best_item = {'identifier': item, 'quantity': quantity} elif current_item_def.billing_method.value == 'unit': if current_item_def.price <= max_cost and current_item_def.price > best_cost: best_cost = current_item_def.price best_item = {'identifier': item, 'quantity': 1} # Calculate the savings and build the list of consumed items savings = self.off/100 * best_cost consumed_items = [] if best_item is not None: consumed_items.append(best_item) consumed_items.append({'identifier': item_def.identifier, 'quantity': get_value(applied_to_item, 'quantity')}) return (round(savings, 2), consumed_items)
def do_delete_order(self): post_data = get_raw_post_data(self) post_dict = parse_json(post_data) order_id = get_value(post_dict, 'id') if order_id is None: set_response(self, 400, 'Must provide id.', 'text/text') else: if datastore.get('orders:' + order_id) is not None: datastore.delete('orders:' + order_id) set_response(self, 200, '') else: set_response(self, 400, 'Order does not exist', 'text/text')
def do_get_item(self): url_query = parse_url_query(self.path) identifier = get_value(url_query, 'identifier') # Ensure that the client provided an identifier to look up if identifier is None or identifier == '': set_response(self, 400, 'Must provide identifier.', 'text/text') else: item = datastore.get('itemdetails:' + identifier, None) if item is not None: # Return the item to the user as json set_response(self, 200, item.to_json()) else: # If the identifier isn't an identifier from an item, tell the client there # was a problem set_response(self, 400, 'Item does not exist.', 'text/text')
def do_post_create_order(self): post_data = get_raw_post_data(self) post_dict = parse_json(post_data) order_id = get_value(post_dict, 'id') # Ensure the client provided an id to create if order_id is None or order_id == '': set_response(self, 400, 'Must provide id.', 'text/text') else: # If the order already exists, we don't want to overwrite it # Instead, tell the user that there was a problem if datastore.get('orders:' + order_id) is None: order = MakeOrder(order_id, datastore) datastore.set('orders:' + order_id, order) set_response(self, 200, '') else: set_response(self, 400, 'Order with that id already exists.', 'text/text')
def test_get_value_when_key_not_in_dict_return_default(self): d = {'test': 'value'} self.assertEqual(H.get_value(d, 'key', 'test_default'), 'test_default')
def calculate_total_with_specials_optimal(self): max_savings = 0.00 # Get all permutations of special items and a list of the remaining items that don't # have specials (specials_perms, non_special_items ) = self.get_special_permutations_and_remainder_items() # Iterate over each permutation to see which special application order # yields the most savings for special_perm_instance in specials_perms: instance_savings = 0.00 # Convert the tuple into a list of items special_perm_instance = list(special_perm_instance) # Compute the current item list instance instance_items = flatten(special_perm_instance, non_special_items) instance_items_dict = merge_item_dict_lists_to_dict( special_perm_instance, non_special_items) # We want to use the list to iterate over because it ensures order # whereas the dict does not. This will calculate the quantity saved by # a given special permutation for item_dict in instance_items: item = get_value(item_dict, 'identifier') quantity = get_value(instance_items_dict, item) # The item must have been fully consumed already and cannot be # considered anymore if quantity is None or quantity == 0: # coverage code analysis program says this line # doesn't run, but putting a print statement in here # and running the tests shows that it does run continue # Get the current item's definition item_def = datastore.get('itemdetails:' + item) # Only compute savings for the special if the item has a special if item_def.special is not None: # Find the quantity the customer can save from this item's special # and which items are involved in the special (new_savings, items_consumed ) = item_def.special.calculate_best_savings( { 'identifier': item, 'quantity': quantity }, instance_items_dict, datastore) # Account for the savings instance_savings += new_savings # Remove any consumed items from the instance item dict for item_consumed in items_consumed: consumed_name = get_value(item_consumed, 'identifier') instance_items_dict[consumed_name] -= get_value( item_consumed, 'quantity') # If all of this item has been consumed by specials, remove it from the # possible items to consume or be used in the future if instance_items_dict[consumed_name] == 0: del instance_items_dict[consumed_name] # Update the maximum savings if this instance saved the customer the most money if instance_savings > max_savings: max_savings = instance_savings # Return the original order total minus the savings return round(self.calculate_total_no_specials() - max_savings, 2)
def test_get_value_when_dict_is_empty_return_none(self): d = {} self.assertEqual(H.get_value(d, 'key'), None)
def test_get_value_when_key_not_in_dict_return_none(self): d = {'test': 'value'} self.assertEqual(H.get_value(d, 'key'), None)
def test_get_value_when_key_in_dict_return_value(self): d = {'test': 'value'} self.assertEqual(H.get_value(d, 'test'), 'value')
def __init__(self, identifier, price, billing_method, special): self.identifier = identifier self.price = price if billing_method.lower() == 'weight': self.billing_method = Methods.WEIGHT else: # Default to price per item scanned self.billing_method = Methods.UNIT # Default the special to none self.special = None # Parse the special out based on its type. The validity of # the special has already been verified special_type = get_value(special, 'type') if special_type == 'AforB': buy = get_value(special, 'buy') limit = parse_int(get_value(special, 'limit'), None) quantity = parse_float(get_value(special, 'for'), 0.0) self.special = AforB(buy, quantity, limit) elif special_type == 'buyAgetBforCoff': buy = get_value(special, 'buy') get = get_value(special, 'get') off = parse_float(get_value(special, 'off'), 0.0) limit = parse_int(get_value(special, 'limit'), None) self.special = BuyAgetBforCoff(buy, get, off, limit) elif special_type == 'getEOLforAoff': off = parse_float(get_value(special, 'off'), 0.0) self.special = GetEOLforAoff(off) elif special_type == 'markdown': price = parse_float(get_value(special, 'price'), 0.0) limit = parse_int(get_value(special, 'limit'), None) self.special = Markdown(price, limit)
def get(self, k, default=None): return get_value(self.database, k, default)
def test_get_value_when_value_is_list_returns_the_list(self): d = {'test': ['value', 'list', 123]} self.assertEqual(H.get_value(d, 'test'), ['value', 'list', 123])
def test_get_value_when_key_is_in_dict_dont_return_default_if_set( self): d = {'test': 'entry'} self.assertNotEqual(H.get_value(d, 'test', 123), 123)
def delete(self, k): if get_value(self.database, k) is not None: del self.database[k]
def add_item(self, item, added_quantity): # Get the quantity we currently are holding for this order current_quantity = get_value(self.items, item.identifier, 0) self.items[item.identifier] = current_quantity + added_quantity