Class and Object Terms

The foundations of Object-Oriented Programming is defining a Class

  • In Object-Oriented Programming (OOP), a class is a blueprint for creating an Object. (a data structure). An Object is used like many other Python variables.
  • A Class has ...
    • a collection of data, these are called Attributes and in Python are pre-fixed using the keyword self
    • a collection of Functions/Procedures. These are called *Methods when they exist inside a Class definition.
  • An Object is created from the Class/Template. Characteristics of objects ...
    • an Object is an Instance of the Class/Template
    • there can be many Objects created from the same Class
    • each Object contains its own Instance Data
    • the data is setup by the Constructor, this is the "init" method in a Python class
    • all methods in the Class/Template become part of the Object, methods are accessed using dot notation (object.method())
  • A Python Class allow for the definition of @ decorators, these allow access to instance data without the use of functions ...
    • @property decorator (aka getter). This enables developers to reference/get instance data in a shorthand fashion (object.name versus object.get_name())
    • @name.setter decorator (aka setter). This enables developers to update/set instance data in a shorthand fashion (object.name = "John" versus object.set_name("John"))
    • observe all instance data (self._name, self.email ...) are prefixed with "", this convention allows setters and getters to work with more natural variable name (name, email ...)

Class and Object Code

# Werkzeug is a collection of libraries that can be used to create a WSGI (Web Server Gateway Interface)
# A gateway in necessary as a web server cannot communicate directly with Python.
# In this case, imports are focused on generating hash code to protect passwords.
from werkzeug.security import generate_password_hash, check_password_hash
import json

# Define a User Class/Template
# -- A User represents the data we want to manage
class User:    
    # constructor of a User object, initializes the instance variables within object (self)
    def __init__(self, name, uid, password):
        self._name = name    # variables with self prefix become part of the object, 
        self._uid = uid
        self.set_password(password)

    # a name getter method, extracts name from object
    @property
    def name(self):
        return self._name
    
    # a setter function, allows name to be updated after initial object creation
    @name.setter
    def name(self, name):
        self._name = name
    
    # a getter method, extracts email from object
    @property
    def uid(self):
        return self._uid
    
    # a setter function, allows name to be updated after initial object creation
    @uid.setter
    def uid(self, uid):
        self._uid = uid
        
    # check if uid parameter matches user id in object, return boolean
    def is_uid(self, uid):
        return self._uid == uid
    
    @property
    def password(self):
        return self._password[0:10] + "..." # because of security only show 1st characters

    # update password, this is conventional setter
    def set_password(self, password):
        """Create a hashed password."""
        self._password = generate_password_hash(password, method='sha256')

    # check password parameter versus stored/encrypted password
    def is_password(self, password):
        """Check against hashed password."""
        result = check_password_hash(self._password, password)
        return result
    
    # output content using str(object) in human readable form, uses getter
    def __str__(self):
        return f'name: "{self.name}", id: "{self.uid}", psw: "{self.password}"'

    # output command to recreate the object, uses attribute directly
    def __repr__(self):
        return f'Person(name={self._name}, uid={self._uid}, password={self._password})'


# tester method to print users
def tester(users, uid, psw):
    result = None
    for user in users:
        # test for match in database
        if user.uid == uid and user.is_password(psw):  # check for match
            print("* ", end="")
            result = user
        # print using __str__ method
        print(str(user))
    return result
        

# place tester code inside of special if!  This allows include without tester running
if __name__ == "__main__":

    # define user objects
    u1 = User(name='Thomas Edison', uid='toby', password='123toby')
    u2 = User(name='Nicholas Tesla', uid='nick', password='123nick')
    u3 = User(name='Alexander Graham Bell', uid='lex', password='123lex')
    u4 = User(name='Eli Whitney', uid='eli', password='123eli')
    u5 = User(name='Hedy Lemarr', uid='hedy', password='123hedy')

    # put user objects in list for convenience
    users = [u1, u2, u3, u4, u5]

    # Find user
    print("Test 1, find user 3")
    u = tester(users, u3.uid, "123lex")


    # Change user
    print("Test 2, change user 3")
    u.name = "John Mortensen"
    u.uid = "jm1021"
    u.set_password("123qwerty")
    u = tester(users, u.uid, "123qwerty")


    # Make dictionary
    ''' 
    The __dict__ in Python represents a dictionary or any mapping object that is used to store the attributes of the object. 
    Every object in Python has an attribute that is denoted by __dict__. 
    Use the json.dumps() method to convert the list of Users to a JSON string.
    '''
    print("Test 3, make a dictionary")
    json_string = json.dumps([user.__dict__ for user in users]) 
    print(json_string)

    print("Test 4, make a dictionary")
    json_string = json.dumps([vars(user) for user in users]) 
    print(json_string)
Test 1, find user 3
name: "Thomas Edison", id: "toby", psw: "sha256$RGS..."
name: "Nicholas Tesla", id: "nick", psw: "sha256$vNa..."
* name: "Alexander Graham Bell", id: "lex", psw: "sha256$z2J..."
name: "Eli Whitney", id: "eli", psw: "sha256$cz2..."
name: "Hedy Lemarr", id: "hedy", psw: "sha256$k6R..."
Test 2, change user 3
name: "Thomas Edison", id: "toby", psw: "sha256$RGS..."
name: "Nicholas Tesla", id: "nick", psw: "sha256$vNa..."
* name: "John Mortensen", id: "jm1021", psw: "sha256$gBz..."
name: "Eli Whitney", id: "eli", psw: "sha256$cz2..."
name: "Hedy Lemarr", id: "hedy", psw: "sha256$k6R..."
Test 3, make a dictionary
[{"_name": "Thomas Edison", "_uid": "toby", "_password": "sha256$RGS7Kz5XMrtq9YZO$0ece44240f114cacfbaca1ac4aa1469127b5df0d95ac259ae4cdff232f6040c5"}, {"_name": "Nicholas Tesla", "_uid": "nick", "_password": "sha256$vNavbMKADUF0EWqT$546481e0bf7f99c4f4502b99ec8b3c66a3207e23b35dee756f970688e0450148"}, {"_name": "John Mortensen", "_uid": "jm1021", "_password": "sha256$gBzbgtt6ZZsfcJAs$558f5161b3e31a8ab0f7962acf49d87c8b774ece4279b92dc09a53f749894b8b"}, {"_name": "Eli Whitney", "_uid": "eli", "_password": "sha256$cz2isBDdEqYh0JGN$8a07c27328282397ea3d07cbc69cc5585ee0dffb92c7cf648d28b23a7b82a14c"}, {"_name": "Hedy Lemarr", "_uid": "hedy", "_password": "sha256$k6RAfmuUs78w7aXZ$413317f798b8c816188dd980caae5aa3d86043a2d7df000b845d9897344eeb84"}]
Test 4, make a dictionary
[{"_name": "Thomas Edison", "_uid": "toby", "_password": "sha256$RGS7Kz5XMrtq9YZO$0ece44240f114cacfbaca1ac4aa1469127b5df0d95ac259ae4cdff232f6040c5"}, {"_name": "Nicholas Tesla", "_uid": "nick", "_password": "sha256$vNavbMKADUF0EWqT$546481e0bf7f99c4f4502b99ec8b3c66a3207e23b35dee756f970688e0450148"}, {"_name": "John Mortensen", "_uid": "jm1021", "_password": "sha256$gBzbgtt6ZZsfcJAs$558f5161b3e31a8ab0f7962acf49d87c8b774ece4279b92dc09a53f749894b8b"}, {"_name": "Eli Whitney", "_uid": "eli", "_password": "sha256$cz2isBDdEqYh0JGN$8a07c27328282397ea3d07cbc69cc5585ee0dffb92c7cf648d28b23a7b82a14c"}, {"_name": "Hedy Lemarr", "_uid": "hedy", "_password": "sha256$k6RAfmuUs78w7aXZ$413317f798b8c816188dd980caae5aa3d86043a2d7df000b845d9897344eeb84"}]

Hacks

Add new attributes/variables to the Class. Make class specific to your CPT work.

  • Add classOf attribute to define year of graduation
    • Add setter and getter for classOf
  • Add dob attribute to define date of birth
    • This will require investigation into Python datetime objects as shown in example code below
    • Add setter and getter for dob
  • Add instance variable for age, make sure if dob changes age changes
    • Add getter for age, but don't add/allow setter for age
  • Update and format tester function to work with changes

Start a class design for each of your own Full Stack CPT sections of your project

  • Use new code cell in this notebook
  • Define init and self attributes
  • Define setters and getters
  • Make a tester

Start Code for Hacks

from datetime import date

def calculate_age(born):
    today = date.today()
    return today.year - born.year - ((today.month, today.day) < (born.month, born.day))

dob = date(2006, 2, 24)
age = calculate_age(dob)
print(age)
16

Hacks Completion

from werkzeug.security import generate_password_hash, check_password_hash
from datetime import date
import json

class User:    

    def __init__(self, name, uid, password, dob, classOf):
        self._name = name    # variables with self prefix become part of the object, 
        self._uid = uid
        self.set_password(password)
        self._dob = dob
        self._classOf = classOf
    
    @property
    def name(self):
        return self._name
    
    # a setter function, allows name to be updated after initial object creation
    @name.setter
    def name(self, name):
        self._name = name
    
    # a getter method, extracts email from object
    @property
    def uid(self):
        return self._uid
    
    # a setter function, allows name to be updated after initial object creation
    @uid.setter
    def uid(self, uid):
        self._uid = uid
        
    # check if uid parameter matches user id in object, return boolean
    def is_uid(self, uid):
        return self._uid == uid
    
    # dob property is returned as string, to avoid unfriendly outcomes
    @property
    def dob(self):
        dob_string = self._dob.strftime('%m-%d-%Y')
        return dob_string
    
    # dob should be have verification for type date
    @dob.setter
    def dob(self, dob):
        self._dob = dob

    @property
    def classOf(self):
        return self._classOf
    
    # a setter function, allows name to be updated after initial object creation
    @classOf.setter
    def classOf(self, classOf):
        self._classOf = classOf
        
    # age is calculated and returned each time it is accessed
    @property
    def age(self):
        today = date.today()
        return today.year - self._dob.year - ((today.month, today.day) < (self._dob.month, self._dob.day))
    
    # dictionary is customized, removing password for security purposes
    @property
    def dictionary(self):
        dict = {
            "name" : self.name,
            "uid" : self.uid,
            "dob" : self.dob,
            "age" : self.age,
            "class of" : self.classOf
        }
        return dict
    
    # update password, this is conventional setter
    def set_password(self, password):
        """Create a hashed password."""
        self._password = generate_password_hash(password, method='sha256')

    # check password parameter versus stored/encrypted password
    def is_password(self, password):
        """Check against hashed password."""
        result = check_password_hash(self._password, password)
        return result
    
    # output content using json dumps, this is ready for API response
    def __str__(self):
        return json.dumps(self.dictionary)
        

if __name__ == "__main__":
    u1 = User(name='Thomas Edison', uid='toby', password='123toby', dob=date(1847, 2, 11), classOf='1904')
    u2 = User(name='Quandale Dingle', uid='QDingle', password='123Dingle', dob=date(1978, 1, 10), classOf='1996')
    print("JSON ready string:\n", u1, "\n") 
    print("Raw Variables of object:\n", vars(u1), "\n") 
    print("Raw Attributes and Methods of object:\n", dir(u1), "\n")
JSON ready string:
 {"name": "Thomas Edison", "uid": "toby", "dob": "02-11-1847", "age": 175, "class of": "1904"} 

Raw Variables of object:
 {'_name': 'Thomas Edison', '_uid': 'toby', '_password': 'sha256$9eAIl8qiKz2wwI1a$ca8d332364c31678198e12fc682f4249faed3e2f42cdaa8b5964c36956fd5c99', '_dob': datetime.date(1847, 2, 11), '_classOf': '1904'} 

Raw Attributes and Methods of object:
 ['__class__', '__delattr__', '__dict__', '__dir__', '__doc__', '__eq__', '__format__', '__ge__', '__getattribute__', '__gt__', '__hash__', '__init__', '__init_subclass__', '__le__', '__lt__', '__module__', '__ne__', '__new__', '__reduce__', '__reduce_ex__', '__repr__', '__setattr__', '__sizeof__', '__str__', '__subclasshook__', '__weakref__', '_classOf', '_dob', '_name', '_password', '_uid', 'age', 'classOf', 'dictionary', 'dob', 'is_password', 'is_uid', 'name', 'set_password', 'uid'] 

from datetime import date
import json

class Student:
    def __init__(self, name, dob, classOf):
        self.name = name
        self.dob = dob
        self.classOf = classOf
        self.age = self.calculate_age()

    def calculate_age(self):
        today = date.today()
        age = today.year - self.dob.year - ((today.month, today.day) < (self.dob.month, self.dob.day))
        self.age = age
        return age

    def get_dob(self):
        return self.dob
    
    def change_dob(self, new_dob):
        self.dob = new_dob
        self.age = self.calculate_age()
        return f"Date of Birth changed to {self.dob}"
    
    def get_age(self):
        return self.age
    
    def get_classOf(self):
        return self.classOf
    
    def change_classOf(self, new_classOf):
        self.classOf = new_classOf
        return f"Class Of changed to {self.classOf}"
    
    def student_data(self):
        # need to change date to string to send json Data
        send_dob = self.dob.strftime("%m/%d/%Y")
        student_information = {
            "name": self.name,
            "dob": send_dob,
            "age": self.age,
            "classOf": self.classOf
        }
        
        return json.dumps(student_information)

    def __str__(self):
        return f"Name: {self.name}, Age: {self.age}, DOB: {self.dob}, Class Of: {self.classOf}"

# Defining Student
Samit = Student("Samit", date(2006, 2, 24), 2024)

# Printing Json Data and Student Information
print(Samit)
print(Samit.student_data())

# Getting Class Of 
print(f"\nSamit is going to graduate in {Samit.get_classOf()}")

# Setting Class Of
print(f"{Samit.change_classOf(2030)}")
print(f"Samit is now going to graduate in {Samit.get_classOf()}\n")

# Getting Dob
print(f"Samit was born on {Samit.get_dob()}")
print(f"Samit is {Samit.get_age()} years old as of {date.today()}\n")

# Setting Dob
print(Samit.change_dob(date(1881, 2, 24)))
print(f"Samit is {Samit.get_age()} years old as of {date.today()}\n")

# Printing Json Data and Student Information after updates
print(f"New Samit is {Samit}")
print(Samit.student_data())
Name: Samit, Age: 16, DOB: 2006-02-24, Class Of: 2024
{"name": "Samit", "dob": "02/24/2006", "age": 16, "classOf": 2024}

Samit is going to graduate in 2024
Class Of changed to 2030
Samit is now going to graduate in 2030

Samit was born on 2006-02-24
Samit is 16 years old as of 2023-01-16

Date of Birth changed to 1881-02-24
Samit is 141 years old as of 2023-01-16

New Samit is Name: Samit, Age: 141, DOB: 1881-02-24, Class Of: 2030
{"name": "Samit", "dob": "02/24/1881", "age": 141, "classOf": 2030}
import json

class Movie:
    def __init__(self, title, director, release_date, rating, actors):
        self._title = title
        self._director = director
        self._release_date = release_date
        self._rating = rating
        self._actors = actors

    @property
    def title(self):
        return self._title

    @title.setter
    def title(self, title):
        self._title = title

    @property
    def director(self):
        return self._director

    @director.setter
    def director(self, director):
        self._director = director

    @property
    def release_date(self):
        return self._release_date

    @release_date.setter
    def release_date(self, release_date):
        self._release_date = release_date

    @property
    def rating(self):
        return self._rating

    @rating.setter
    def rating(self, rating):
        self._rating = rating

    @property
    def actors(self):
        return self._actors

    @actors.setter
    def actors(self, actors):
        self._actors = actors


movie1 = Movie("The Shawshank Redemption", "Frank Darabont", "1994-10-14", 9.2, ["Tim Robbins", "Morgan Freeman", "Bob Gunton"])

print(movie1.title)  
print(movie1.director)  
print(movie1.release_date)  
print(movie1.rating)  
print(movie1.actors)
The Shawshank Redemption
Frank Darabont
1994-10-14
9.2
['Tim Robbins', 'Morgan Freeman', 'Bob Gunton']

This code defines a Python class called "Movie" that has a constructor and several setters and getters for the class properties.

The constructor is the init method, which sets the initial values for the properties of the class: title, director, release_date, rating, and actors.

The setters and getters are methods that are used to set and get the values of the properties of the class, respectively. They are defined using the @property decorator for the getter and the @property_name.setter decorator for the setter. This way we can easily manipulate the values of the class properties.

With this code, we can create an instance of a movie and set/get the values of its properties like title, director, release date, rating, actors and so on.

For example, we can use the code to create a movie object with the given values and then access the values using the getters. We can also update the values using the setters.

We can also use JSON library to convert this class object into json for sending the data to the backend or storing it locally.

In our project, we can use this class to create Movie object and store the returned data from the TMDb API, and then use the getters to retrieve the data from these objects and display it on the frontend.

Tester

class Inventory:
    def __init__(self, items=[]):
        self.items = items
    
    def add_item(self, item):
        self.items.append(item)
        
    def remove_item(self, item):
        self.items.remove(item)
        
    def get_items(self):
        return self.items
    
    def to_json(self):
        return json.dumps(self.items)

# Tester
inventory = Inventory()
item1 = {"name": "item1", "quantity": 10}
item2 = {"name": "item2", "quantity": 20}

inventory.add_item(item1)
inventory.add_item(item2)

print(inventory.get_items()) # [{"name": "item1", "quantity": 10}, {"name": "item2", "quantity": 20}]

inventory.remove_item(item1)

print(inventory.get_items()) # [{"name": "item2", "quantity": 20}]

print(inventory.to_json()) # [{"name": "item2", "quantity": 20}]

This is a simple example of how you could use object-oriented programming in Python to create an Inventory class that has methods for adding and removing items, as well as getting and returning the items as a json object. The tester creates an instance of the Inventory class, adds two items, removes one item and checks the items in the inventory and json representation of it.