<SYSTEM_TASK:>
Given the following text description, write Python code to implement the functionality described below step by step
<END_TASK>
<USER_TASK:>
Description:
 Week 5 - Crafting the public interface.
Learning Objectives
Explain what a public interface is
Discuss the advantages of defining a public interface
Compare different public interfaces
Design a simple public interface
Inheritance
Last week we looked at inheritance, building a general class that we could then extend with additional functionality for special situations. 
Each of the classes we create inheriting from our general class can be thought of as having a 'is-a' relationship with the general class. For example, looking at our Item example from last week Equipment is a Item, Consumable is a Item.
Step5:  Composition
In week 3 we took example projects and broke them down into a collection of different classes. Many of you chose the cookbook example for the assignment and questioned whether things like ingredients should be attributes on the recipe class or classes in their own right. Often the answer is both. These are the interactions that change a collection of different classes into a functioning program. This is called composition. The Recipe object is a composite object, it has ingredients, it has instructions, etc.
This week we will look at how we can design our classes to be easy to use, for both programmer-class and class-class interactions.
Step6:  This has the basic functionality implemented but there are some improvements we can make. 
Before we look at making changes we can seek inspiration. Requests and Pandas are two packages well regarded for having well implemented interfaces.
Requests
Step7:  The API documentation for requests
The Response class
Some useful features
Step12:  The API documentation for the DataFrame object.
The actual code.
Some useful features
Step17:  Viewing the ingredients now looks much better. Let's now look at the get_nutrition method.
There are still a number of areas that could be improved
When we call get_nutrition it is not clear what the different values returned actually are
We don't use the get_nutrition method when calculating the nutrition values in the Recipe class
There is no way to add additional types of nutrient
Ingredient and Recipe return different types from get_nutrition, tuple and list respectively
Recipe could not be used as an ingredient for another Recipe
Step18:  WSGI
The value of building and documenting a interface to our code is not unique to object oriented programming.
Next week we will look at creating websites as an alternative to command line programs and GUIs. Python has a rich ecosystem of web servers and frameworks for creating web applications. Importantly, the vast majority use a common interface called WSGI. 
WSGI is based on a simple exchange. The example below use the wsgiref package for the web server with the application implemented without using external packages. Next week, we will look at some of the more commonly used web servers and use a web framework to develop a more substantial web project.
Step24:  Assignments
Modify the Ingredient and Recipe classes so that the following code works.
<ASSISTANT_TASK:>
Python Code:
class Item(object):
    
    
    def __init__(self, name, description, location):
        self.name = name
        self.description = description
        self.location = location
        
        
    def update_location(self, new_location):
        pass
class Equipment(Item):
    pass
class Consumable(Item):
    
    
    def __init__(self, name, description, location, initial_quantity, current_quantity, storage_temp, flammability):
        self.name = name
        self.description = description
        self.location = location
        self.initial_quantity = initial_quantity
        self.current_quantity = current_quantity
        self.flammability = flammability
        
        
    def update_quantity_remaining(self, amount):
        pass
Explanation: Week 5 - Crafting the public interface.
Learning Objectives
Explain what a public interface is
Discuss the advantages of defining a public interface
Compare different public interfaces
Design a simple public interface
Inheritance
Last week we looked at inheritance, building a general class that we could then extend with additional functionality for special situations. 
Each of the classes we create inheriting from our general class can be thought of as having a 'is-a' relationship with the general class. For example, looking at our Item example from last week Equipment is a Item, Consumable is a Item.
End of explanation
class Ingredient(object):
    The ingredient object that contains nutritional information
    
    def __init__(self, name, carbs, protein, fat):
        self.name = name
        self.carbs = carbs
        self.protein = protein
        self.fat = fat
        
    
    def get_nutrition(self):
        Returns the nutritional information for the ingredient
        return (self.carbs, self.protein, self.fat)
    
class Recipe(object):
    The Recipe object containing the ingredients
    
    def __init__(self, name, ingredients):
        self.name = name
        self.ingredients = ingredients
        
        
    def get_nutrition(self):
        Returns the nutritional information for the recipe
        nutrition = [0, 0, 0]
        for amount, ingredient in self.ingredients:
            nutrition[0] += amount * ingredient.carbs
            nutrition[1] += amount * ingredient.protein
            nutrition[2] += amount * ingredient.fat
        return nutrition
    
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)), 
                         (30, Ingredient('Oil', 0, 0, 1)), 
                         (36, Ingredient('Sugar', 1, 0, 0)), 
                         (7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
                         (560, Ingredient('Water', 0, 0, 0))])
print(bread.ingredients)
print(bread.get_nutrition())
Explanation: Composition
In week 3 we took example projects and broke them down into a collection of different classes. Many of you chose the cookbook example for the assignment and questioned whether things like ingredients should be attributes on the recipe class or classes in their own right. Often the answer is both. These are the interactions that change a collection of different classes into a functioning program. This is called composition. The Recipe object is a composite object, it has ingredients, it has instructions, etc.
This week we will look at how we can design our classes to be easy to use, for both programmer-class and class-class interactions.
End of explanation
import requests
r = requests.get('https://api.github.com/repos/streety/biof509/events')
print(r.status_code)
print(r.headers['content-type'])
print(r.text[:1000])
print(r.json()[0]['payload']['commits'][0]['message'])
type(r)
Explanation: This has the basic functionality implemented but there are some improvements we can make. 
Before we look at making changes we can seek inspiration. Requests and Pandas are two packages well regarded for having well implemented interfaces.
Requests: HTTP for Humans
Requests is a package used for making HTTP requests. There are options in the python standard library for making http requests but they can seem difficult to use.
End of explanation
import pandas as pd
data = pd.DataFrame([[0,1,2,3], [4,5,6,7], [8,9,10,11]], index=['a', 'b', 'c'], columns=['col1', 'col2', 'col3', 'col4'])
data
print(data.shape)
print(data['col1'])
print(data.col1)
import matplotlib.pyplot as plt
%matplotlib inline
data.plot()
data.to_csv('Wk05-temp.csv')
data2 = pd.read_csv('Wk05-temp.csv', index_col=0)
print(data2)
Explanation: The API documentation for requests
The Response class
Some useful features:
property
Pandas
pandas is an open source, BSD-licensed library providing high-performance, easy-to-use data structures and 
data analysis tools for the Python programming language.
End of explanation
class Ingredient(object):
    The ingredient object that contains nutritional information
    
    def __init__(self, name, carbs, protein, fat):
        self.name = name
        self.carbs = carbs
        self.protein = protein
        self.fat = fat
        
    
    def __repr__(self):
        return 'Ingredient({0}, {1}, {2}, {3})'.format(self.name, self.carbs, self.protein, self.fat)
    
    
    def get_nutrition(self):
        Returns the nutritional information for the ingredient
        return (self.carbs, self.protein, self.fat)
    
class Recipe(object):
    The Recipe object containing the ingredients
    
    def __init__(self, name, ingredients):
        self.name = name
        self.ingredients = ingredients
        
        
    def get_nutrition(self):
        Returns the nutritional information for the recipe
        nutrition = [0, 0, 0]
        for amount, ingredient in self.ingredients:
            nutrition[0] += amount * ingredient.carbs
            nutrition[1] += amount * ingredient.protein
            nutrition[2] += amount * ingredient.fat
        return nutrition
    
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)), 
                         (30, Ingredient('Oil', 0, 0, 1)), 
                         (36, Ingredient('Sugar', 1, 0, 0)), 
                         (7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
                         (560, Ingredient('Water', 0, 0, 0))])
print(bread.ingredients)
print(bread.get_nutrition())
Explanation: The API documentation for the DataFrame object.
The actual code.
Some useful features:
* classmethod
* property
* __getitem__
* Public and private attributes/methods
* __getattr__
Cookbook
We can now return to our cookbook example.
Displaying the ingredients needs to be improved.
End of explanation
class Ingredient(object):
    The ingredient object that contains nutritional information
    
    def __init__(self, name, carbs, protein, fat):
        self.name = name
        self.carbs = carbs
        self.protein = protein
        self.fat = fat
        
    
    def __repr__(self):
        return 'Ingredient({0}, {1}, {2}, {3})'.format(self.name, self.carbs, self.protein, self.fat)
    
    
    def get_nutrition(self):
        Returns the nutritional information for the ingredient
        return (self.carbs, self.protein, self.fat)
    
class Recipe(object):
    The Recipe object containing the ingredients
    
    def __init__(self, name, ingredients):
        self.name = name
        self.ingredients = ingredients
        
        
    def get_nutrition(self):
        Returns the nutritional information for the recipe
        nutrition = [0, 0, 0]
        for amount, ingredient in self.ingredients:
            nutrition[0] += amount * ingredient.carbs
            nutrition[1] += amount * ingredient.protein
            nutrition[2] += amount * ingredient.fat
        return nutrition
    
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)), 
                         (30, Ingredient('Oil', 0, 0, 1)), 
                         (36, Ingredient('Sugar', 1, 0, 0)), 
                         (7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
                         (560, Ingredient('Water', 0, 0, 0))])
print(bread.ingredients)
print(bread.get_nutrition())
Explanation: Viewing the ingredients now looks much better. Let's now look at the get_nutrition method.
There are still a number of areas that could be improved
When we call get_nutrition it is not clear what the different values returned actually are
We don't use the get_nutrition method when calculating the nutrition values in the Recipe class
There is no way to add additional types of nutrient
Ingredient and Recipe return different types from get_nutrition, tuple and list respectively
Recipe could not be used as an ingredient for another Recipe
End of explanation
!cat Wk05-wsgi.py
Explanation: WSGI
The value of building and documenting a interface to our code is not unique to object oriented programming.
Next week we will look at creating websites as an alternative to command line programs and GUIs. Python has a rich ecosystem of web servers and frameworks for creating web applications. Importantly, the vast majority use a common interface called WSGI. 
WSGI is based on a simple exchange. The example below use the wsgiref package for the web server with the application implemented without using external packages. Next week, we will look at some of the more commonly used web servers and use a web framework to develop a more substantial web project.
End of explanation
class Ingredient(object):
    The ingredient object that contains nutritional information
    
    
    def __init__(self, name, *args, **kwargs):
        self.name = name
        self.nums = []
        for a in [*args]:
            if isinstance(a, dict):
                for key in a.keys():
                    setattr(self, key, a[key])
            elif isinstance(a, float):
                self.nums.append(a)
                if len(self.nums) in [3,4]:
                    for n, val in zip(['carbs', 'protein', 'fat', 'cholesterol'], self.nums):
                        setattr(self, n, val)
            elif isinstance(a, int):
                self.nums.append(a)
                if len(self.nums) in [3,4]:
                    for n, val in zip(['carbs', 'protein', 'fat', 'cholesterol'], self.nums):
                        setattr(self, n, val)            
            else:
                print('Need correct nutritional information format')
    
    
    def __repr__(self):
        if getattr(self, 'cholesterol', False):
            return 'Ingredient({0}, {1}, {2}, {3}, {4})'.format(self.name, 
                                                                self.carbs, 
                                                                self.protein, 
                                                                self.fat, 
                                                                self.cholesterol)        
        else:
            return 'Ingredient({0}, {1}, {2}, {3})'.format(self.name,
                                                          self.carbs,
                                                          self.protein,
                                                          self.fat)
    
    
    def get_nutrition(self):
        Returns the nutritional information for the ingredient
        return (self.carbs, self.protein, self.fat, self.cholestrol)
    
    
    def get_name(self):
        Returns the ingredient name
        return self.name
    
class Recipe(object):
    The Recipe object containing the ingredients
    
    
    def __init__(self, name, *ingredients):
        self.name = name
        self.ingredients = [*ingredients][0]
        self.number = len(*ingredients)
        self.nutrition_ = {'carbs': 0, 'protein': 0, 'fat':0, 'cholesterol':0}
            
            
    def __repr__(self):
        return 'Recipe({0}, {1})'.format(self.name, self.ingredients)
        
        
    def get_nutrition(self):
        Returns the nutritional information for the recipe
        #for _ in range(self.number):
        nutrition = [0,0,0,0] # need to be length of dict
        for amount, ingredient in self.ingredients:
            # print(type(ingredient), ingredient) # test
            try:
                if getattr(ingredient, 'cholesterol', False):
                    nutrition[0] += amount * ingredient.carbs
                    nutrition[1] += amount * ingredient.protein
                    nutrition[2] += amount * ingredient.fat
                    nutrition[3] += amount * ingredient.cholesterol
                else:
                    nutrition[0] += amount * ingredient.carbs
                    nutrition[1] += amount * ingredient.protein
                    nutrition[2] += amount * ingredient.fat
            except AttributeError: # in case another recipe is in the ingredients (nested)
                nu = ingredient.get_nutrition()
                nu = [amount * x for x in nu]
                nutrition[0] += nu[0]
                nutrition[1] += nu[1]
                nutrition[2] += nu[2]
                nutrition[3] += nu[3]
        return nutrition
    
    @property
    def nutrition(self):
        facts = self.get_nutrition()
        self.nutrition_['carbs'] = facts[0]
        self.nutrition_['protein'] = facts[1]
        self.nutrition_['fat'] = facts[2]
        self.nutrition_['cholesterol'] = facts[3]
        return self.nutrition_
    
    
    def get_name(self):
        return self.name
bread = Recipe('Bread', [(820, Ingredient('Flour', 0.77, 0.10, 0.01)), 
                         (30, Ingredient('Oil', 0, 0, 1)), 
                         (36, Ingredient('Sugar', 1, 0, 0)), 
                         (7, Ingredient('Yeast', 0.3125, 0.5, 0.0625)),
                         (560, Ingredient('Water', 0, 0, 0))])
print(bread.ingredients)
# Should be roughly [(820, Ingredient(Flour, 0.77, 0.1, 0.01)), (30, Ingredient(Oil, 0, 0, 1)), 
# (36, Ingredient(Sugar, 1, 0, 0)), (7, Ingredient(Yeast, 0.3125, 0.5, 0.0625)), (560, Ingredient(Water, 0, 0, 0))]
print(bread.nutrition)
#Should be roughly {'carbs': 669.5875, 'protein': 85.5, 'fat': 38.6375} the order is not important
eggs = Ingredient('Egg', {'carbs': 0.0077, 'protein': 0.1258, 'fat': 0.0994, 'cholesterol': 0.00423, 'awesome':100})
#eggs = Ingredient('Egg', {'carbs': 0.0077, 'protein': 0.1258, 'fat': 0.0994})
#eggs = Ingredient('Egg', 0.0077, 0.1258, 0.0994, 0.00423)
print(eggs)
#Points to note:
# - The different call to Ingredient, you can use isinstance or type to change the 
#   behaviour depending on the arguments supplied
# - Cholesterol as an extra nutrient, your implementation should accept any nutrient
# - Use of Recipe (bread) as an ingredient
basic_french_toast = Recipe('Basic French Toast', [(300, Ingredient('Egg', {'carbs': 0.0077, 'protein': 0.1258, 
                                                                            'fat': 0.0994, 'cholesterol': 0.00423})), 
                                                  (0.25, bread)])
print(basic_french_toast.ingredients)
# Should be roughly:
# [(300, Ingredient(Egg, 0.0077, 0.1258, 0.0994)), (0.25, Recipe(Bread, [(820, Ingredient(Flour, 0.77, 0.1, 0.01)), 
# (30, Ingredient(Oil, 0, 0, 1)), (36, Ingredient(Sugar, 1, 0, 0)), (7, Ingredient(Yeast, 0.3125, 0.5, 0.0625)), 
# (560, Ingredient(Water, 0, 0, 0))]))]
# Note the formatting for the Recipe object, a __repr__ method will be needed
print(basic_french_toast.nutrition)
# Should be roughly {'protein': 59.115, 'carbs': 169.706875, 'cholesterol': 1.2690000000000001, 'fat': 39.479375000000005}
# The order is not important
Explanation: Assignments
Modify the Ingredient and Recipe classes so that the following code works.
End of explanation
<END_TASK>