from werkzeug.security import generate_password_hash, check_password_hash
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
    
    @name.setter
    def name(self, name):
        self._name = name
    
    @property
    def uid(self):
        return self._uid
    
    @uid.setter
    def uid(self, uid):
        self._uid = uid
        
    def is_uid(self, uid):
        return self._uid == uid
    
    @property
    def dob(self):
        dob_string = self._dob.strftime('%m-%d-%Y')
        return dob_string
    
    @dob.setter
    def dob(self, dob):
        self._dob = dob
        
    @property
    def classOf(self):
        return self._classOf
    
    @classOf.setter
    def classOf(self, classOf):
        self._classOf = classOf
        
    @property
    def age(self):
        today = date.today()
        return today.year - self._dob.year - ((today.month, today.day) < (self._dob.month, self._dob.day))
    
    @property
    def dictionary(self):
        dict = {
            "name" : self.name,
            "uid" : self.uid,
            "dob" : self.dob,
            "classOf" : self.classOf,
            "age" : self.age
        }
        return dict
    
    def set_password(self, password):
        """Create a hashed password."""
        self._password = generate_password_hash(password, method='sha256')

        def is_password(self, password):"""Check against hashed password."""
        result = check_password_hash(self._password, password)
        return result
    
    def __str__(self):
        return json.dumps(self.dictionary)
    
    def __repr__(self):
        return f'User(name={self._name}, uid={self._uid}, password={self._password},dob={self._dob}, classOf={self._classOf})'
    

if __name__ == "__main__":
    u1 = User(name='Nikhil Chakravarthula', uid='toby', password='123toby', dob=date(2007, 6, 8), classOf=2025)
    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")
    print("Representation to Re-Create the object:\n", repr(u1), "\n") 
JSON ready string:
 {"name": "Nikhil Chakravarthula", "uid": "toby", "dob": "06-08-2007", "classOf": 2025, "age": 15} 

Raw Variables of object:
 {'_name': 'Nikhil Chakravarthula', '_uid': 'toby', '_password': 'sha256$7BBZmo1GO1EXFYog$54b62df017fa8f0cbd73aea9593b41e005319bc51d5b318f8a000f28c0a2a7ab', '_dob': datetime.date(2007, 6, 8), '_classOf': 2025} 

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_uid', 'name', 'set_password', 'uid'] 

Representation to Re-Create the object:
 User(name=Nikhil Chakravarthula, uid=toby, password=sha256$7BBZmo1GO1EXFYog$54b62df017fa8f0cbd73aea9593b41e005319bc51d5b318f8a000f28c0a2a7ab,dob=2007-06-08, classOf=2025) 

This code defines a class User that represents a user of some system.

The init method is the constructor for the class and is called when an object of the class is created. It takes in several arguments, such as name, uid, password, dob (date of birth), and classOf and assigns them to instance variables with the self prefix. These instance variables are associated with the object and can be accessed and modified through the object's methods.

The class also has several properties, which are methods decorated with @property. These properties allow for read-only access to the instance variables without allowing direct modification. For example, the name property returns the value of self._name, but does not allow the user to directly change the value of self._name. To change the value, the class also has setter methods, which are decorated with @name.setter.

The class also has methods such as is_uid, set_password, and is_password that perform specific tasks such as checking if a given uid matches the object's uid, setting a hashed password for the object, and checking if a given password matches the object's hashed password.

The str method is a special method that is called when the object is passed to the print() function or when the str() function is called on the object. It returns a JSON ready string representation of the object.

The repr method is another special method that is called when the repr() function is called on the object. It returns a string that can be used to re-create the object.

Finally, the code has a few print statements that demonstrate how to use these methods. For example, print("JSON ready string:\n", u1, "\n") prints the JSON ready string representation of the object u1.

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(2004, 12, 31)
age = calculate_age(dob)
print(age)
18
import json

class Car:
    def __init__(self, make, model, price, year):
        self._make = make
        self._model = model
        self._price = price
        self._year = year

    @property
    def make(self):
        return self._make

    @make.setter
    def make(self, make):
        self._make = make

    @property
    def model(self):
        return self._model

    @model.setter
    def model(self, model):
        self._model = model

    @property
    def price(self):
        return self._price

    @price.setter
    def price(self, price):
        self._price = price

    @property
    def year(self):
        return self._year

    @year.setter
    def year(self, year):
        self._year = year

    def calculate_depreciation(self, years):
        depreciation = 0.9**years * self._price
        return depreciation

    @property
    def dictionary(self):
        car_dict = {
            "make": self.make,
            "model": self.model,
            "price": self.price,
            "year": self.year
        }
        return car_dict

    def __str__(self):
        return json.dumps(self.dictionary)

if __name__ == "__main__":
    car1 = Car(make="Tesla", model="Model X", price="$110,000", year="2016")
    print(car1)
{"make": "Tesla", "model": "Model X", "price": "$110,000", "year": "2016"}

This code defines a class called Car that represents a car object.

The init method is the constructor for the class and is called when an object of the class is created. It takes in four arguments, such as make, model, price, and year and assigns them to instance variables with the self prefix. These instance variables are associated with the object and can be accessed and modified through the object's methods.

The class also has several properties, which are methods decorated with @property. These properties allow for read-only access to the instance variables without allowing direct modification. For example, the make property returns the value of self._make, but does not allow the user to directly change the value of self._make. To change the value, the class also has setter methods, which are decorated with @make.setter.

The class also has a method called calculate_depreciation, which takes in a single argument years and calculates the depreciation of the car based on that many years using the formula: 0.9^years * self._price

The class also has a property called dictionary that returns a dictionary representation of the car's attributes, make, model, price, and year.

The str method is a special method that is called when the object is passed to the print() function or when the str() function is called on the object. It uses the json.dumps() function to return a JSON string representation of the object.

Finally, the code has a simple test that creates a Car object, car1, and prints it, to see the object in the form of json string.