Database and SQLAlchemy

In this blog we will explore using programs with data, focused on Databases. We will use SQLite Database to learn more about using Programs with Data. Use Debugging through these examples to examine Objects created in Code.

  • College Board talks about ideas like

    • Program Usage. "iterative and interactive way when processing information"
    • Managing Data. "classifying data are part of the process in using programs", "data files in a Table"
    • Insight "insight and knowledge can be obtained from ... digitally represented information"
    • Filter systems. 'tools for finding information and recognizing patterns"
    • Application. "the preserve has two databases", "an employee wants to count the number of book"
  • PBL, Databases, Iterative/OOP

    • Iterative. Refers to a sequence of instructions or code being repeated until a specific end result is achieved
    • OOP. A computer programming model that organizes software design around data, or objects, rather than functions and logic
    • SQL. Structured Query Language, abbreviated as SQL, is a language used in programming, managing, and structuring data

Imports and Flask Objects

Defines and key object creations

  • Comment on where you have observed these working? Provide a defintion of purpose.
    1. Flask app object I saw the Flask app object in our CPT project, where it managed many if not all operations that we wanted to perform with flask
    2. SQLAlchemy db object The db object was also created in our CPT project, where it was used to establish a connection and perform database operations and manipulations on our data
"""
These imports define the key objects
"""

from flask import Flask
from flask_sqlalchemy import SQLAlchemy

"""
These object and definitions are used throughout the Jupyter Notebook.
"""

# Setup of key Flask object (app)
app = Flask(__name__)
# Setup SQLAlchemy object and properties for the database (db)
database = 'sqlite:///sqlite.db'  # path and filename of database
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SQLALCHEMY_DATABASE_URI'] = database
app.config['SECRET_KEY'] = 'SECRET_KEY'
db = SQLAlchemy()


# This belongs in place where it runs once per project
db.init_app(app)

Model Definition

Define columns, initialization, and CRUD methods for users table in sqlite.db

  • Comment on these items in the class, purpose and defintion.
    • class User The User abstraction encapsulates all the user attributes, such as uid and name, along with various methods like create, add, delete, and update. This abstraction is useful as it centralizes all the necessary information and operations related to users under a single name, making it convenient to modify user details and update their entry in the program at a later stage.
    • db.Model inheritance By inheriting all the methods and attributes from the db.Model class, we can utilize data types for our database schema, as well as other CRUD (Create, Read, Update, Delete) operations.
    • init method This serves as our class constructor, responsible for initializing the user attributes when the object is created, so that we can utilize this data in our subsequent operations.
    • @property, @<column>.setter These decorators are employed to define the getters and setters within a class or object, which generates references that can be utilized to access the class methods and attributes from outside the class.
    • create, read, update, delete methods Methods are used to help to solve problems with a class or to change things within the table.
""" database dependencies to support sqlite examples """
import datetime
from datetime import datetime
import json

from sqlalchemy.exc import IntegrityError


''' Tutorial: https://www.sqlalchemy.org/library.html#tutorials, try to get into a Python shell and follow along '''

# Define the User class to manage actions in the 'users' table
# -- Object Relational Mapping (ORM) is the key concept of SQLAlchemy
# -- a.) db.Model is like an inner layer of the onion in ORM
# -- b.) User represents data we want to store, something that is built on db.Model
# -- c.) SQLAlchemy ORM is layer on top of SQLAlchemy Core, then SQLAlchemy engine, SQL
class Pong(db.Model):
    __tablename__ = 'pongs'  

    # Define the Pong schema with "vars" from object
    id = db.Column(db.Integer, primary_key=True)
    _user1 = db.Column(db.String(255), unique=False, nullable=False)
    _user2 = db.Column(db.String(255), unique=False, nullable=False)
    _score1 = db.Column(db.String(255), unique=False, nullable=False)
    _score2 = db.Column(db.String(255), unique=False, nullable=False)
    _result1 = db.Column(db.String(255), unique=False, nullable=False)
    _result2 = db.Column(db.String(255), unique=False, nullable=False)
    _gameDatetime = db.Column(db.DateTime)

    # constructor of a Pong object, initializes the instance variables within object (self)
    def __init__(self, user1="none", user2="none", score1='0', score2='0', result1="none", result2="none", gameDatetime=datetime.now()): # variables with self prefix become part of the object, 
        self._user1 = user1
        self._user2 = user2
        self._score1 = score1
        self._score2 = score2
        self._result1 = result1
        self._result2 = result2
        self._gameDatetime = gameDatetime
    
    # a getter method, extracts email from object
    @property
    def user1(self):
        return self._user1
    
    # a setter function, allows user1 to be updated after initial object creation. This one for player 1
    @user1.setter
    def user1(self, user1):
        self._user1 = user1
        
    @property
    def user2(self):
        return self._user2
    
    # a setter function for player 2
    @user2.setter
    def user2(self, user2):
        self._user2 = user2
    
    @property
    def score1(self):
        return self._score1
    
    # a setter function for score for player 1
    @score1.setter
    def score1(self, score1):
        self._score1 = score1
    
    @property
    def score2(self):
        return self._score2
    
    # a setter function for score for player 2
    @score2.setter
    def score2(self, score2):
        self._score2 = score2

    @property
    def result1(self):
        return self._result1
    
    # a setter function for the result of player 1
    @result1.setter
    def result1(self, result1):
        self._result1 = result1

    @property
    def result2(self):
        return self._result2
    
    # a setter function for the result of player 1
    @result2.setter
    def result2(self, result2):
        self._result2 = result2
    
    # gameDatetime property is returned as string
    @property
    def gameDatetime(self):
        gameDatetime_string = self._gameDatetime.strftime('%m-%d-%Y %H:%M:%S')
        return gameDatetime_string
    
    # gameDatetime should be have verification for type date
    @gameDatetime.setter
    def gameDatetime(self, gameDatetime):
        self._gameDatetime = gameDatetime
    
    # output content using str(object) in human readable form, uses getter
    # output content using json dumps, this is ready for API response
    def __str__(self):
        return json.dumps(self.make_dict())

    # CRUD create/add a new record to the table
    # returns self or None on error
    def create(self):
        try:
            # creates a person object from Pong(db.Model) class, passes initializers
            db.session.add(self)  # add prepares to persist person object to Pongs table
            db.session.commit()  # SqlAlchemy "unit of work pattern" requires a manual commit
            return self
        except IntegrityError:
            db.session.remove()
            return None
    
    # CRUD update: updates user1, user2, score1, score2, result1, result2
    # returns self
    def update(self, user1="", user2="", score1="", score2="", result1="", result2=""):
        """only updates values with length"""
        if len(user1) == 3:
            self.user1 = user1
        if len(user2) == 3:
            self.user2 = user2
        """only updates values with scores greater than 0"""
        if int(score1) >= 0:
            self.score1 = score1
        if int(score2) >= 0:
            self.score2 = score2
        """only updates when the results are either won or loss"""
        if result1 in ("Win", "Loss"):
            self.result1 = result1
        if result2 in ("Win", "Loss"):
            self.result2 = result2
        db.session.commit()
        return self

    # CRUD delete: remove self
    # None
    def delete(self):
        db.session.delete(self)
        db.session.commit()
        return None

Initial Data

Uses SQLALchemy db.create_all() to initialize rows into sqlite.db

  • Comment on how these work?

    1. Create All Tables from db Object calls the create_all() method from the db object created in the model class. I assume that takes the Object properties and generates the table with the object entries.

    2. User Object Constructors defined with init(), takes in the inputted data and assigns it to class variables for future operations such as adding to database.

    3. Try / Except The try method runs the code within it first and runs the create function from the above code, creating the data for each of the user within users then prints the uid of the object created. If the try method does no work, then it goes to the code within except, which is the print

"""Database Creation and Testing """


def initPong():
    with app.app_context():
        """Create database and tables"""
        db.create_all()
        """Tester data for table"""
        game1 = Pong('AAA', 'BBB', '1', '5', 'Loss', 'Win', datetime(2023, 1, 22, 15, 30, 0))
        game2 = Pong('AAB', 'ABC', '2', '5', 'Loss', 'Win', datetime(2023, 1, 21, 14, 15, 0))
        game3 = Pong('AAC', 'GHI', '5', '4', 'Win', 'Loss', datetime(2023, 1, 20, 13, 0, 0))
        game4 = Pong('AAD', 'FGH', '5', '1', 'Win', 'Loss', datetime(2023, 1, 19, 12, 45, 0))
        game5 = Pong('AAE', 'TYU', '3', '5', 'Loss', 'Win', datetime(2023, 1, 22, 11, 30, 0))

        games = [game1, game2, game3, game4, game5]

        """Builds sample game data"""
        for game in games:
            try:
                game.create()
            except IntegrityError:
                '''fails with bad data'''
                db.session.remove()
                print(f"Error in {game.user1} and/or {game.user2}")                

initPong()

Check for given Credentials in users table in sqlite.db

Use of ORM Query object and custom methods to identify user to credentials uid and password

  • Comment on purpose of following
    1. User.query.filter_by Locates the first entry in the database where the value of the _uid coloumn is equal to a specific UID. Used to search for the object of a user to update.
    2. user.password accesses the password of the user.
def find_by_uid(user1):
    with app.app_context():
        user = Pong.query.filter_by(_user1=user1).first()
    return user # returns user object

# Check credentials by finding user and verify password
def check_result(user1, result1):
    # query email and return user record
    user = find_by_uid(user1)
    if user == None:
        return False
    return False
        
check_result("AAA", "Loss")
False

Create a new User in table in Sqlite.db

Uses SQLALchemy and custom user.create() method to add row.

  • Comment on purpose of following
    1. user.find_by_uid() and try/except Locates and stores the object of the user in question. Try printing the user information to check if the user has been created, if NotFound error, we continue with the rest of the function
    2. user = User(...) Creates the user object with the inputted parameters
    3. user.dob and try/except Set the date of birth of our user. Try to format the date into a standard format, if an invalid input is detected, autofill with today's date
    4. user.create() and try/except creates the user object instantiated in the create() function with it's method in the Model class. This inserts our object into the database.
def create():
    # optimize user time to see if uid exists
    user1 = input("Enter your user id:")
    game = find_by_uid(user1)
    try:
        print("Found\n", game.read())
        return
    except:
        pass # keep going
    
    # request value that ensure creating valid object
    user1 = input("Enter first username:")
    user2 = input("Enter second username:")
    score1 = input("Enter first score:")
    score2 = input("Enter second score:")
    result1 = input("Enter player 1's result:")
    result2 = input("Enter player 2's result:")
    
    # Initialize User object before date
    game = Pong(user1=user1, 
                user2=user2, 
                score1=score1, 
                score2=score2, 
                result1=result1, 
                result2=result2)
    
    # create user.dob, fail with today as dob
    gameDatetime = input("Enter your date of birth 'YYYY-MM-DD'")
    try:
        game.dog = datetime.strptime(gameDatetime, '%Y-%m-%d %H:%M:%S').date()
    except ValueError:
        game.dog = datetime.today()
        print(f"Invalid date {gameDatetime} require YYYY-mm-dd, date defaulted to {game.gameDatetime}")
           
    # write object to database
    with app.app_context():
        try:
            object = game.create()
            print("Created\n", object.read())
        except:  # error raised if object not created
            print("Unknown error uid {uid}")
        
create()
Unknown error uid {uid}

Reading users table in sqlite.db

Uses SQLALchemy query.all method to read data

  • Comment on purpose of following
    1. User.query.all Grabs a list/iterable of all user entries in the database provided
    2. json_ready assignment, google List Comprehensio Json formatted data. Uses list comprehension which takes all data from a prexisting list and moifies it to a copy that we later use.
# SQLAlchemy extracts all users from database, turns each user into JSON
def read():
    with app.app_context():
        table = Pong.query.all()
    json_ready = [user.read() for user in table] # "List Comprehensions", for each user add user.read() to list
    return json_ready

read()
---------------------------------------------------------------------------
AttributeError                            Traceback (most recent call last)
/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb Cell 14 in <cell line: 8>()
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=4'>5</a>     json_ready = [user.read() for user in table] # "List Comprehensions", for each user add user.read() to list
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=5'>6</a>     return json_ready
----> <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=7'>8</a> read()

/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb Cell 14 in read()
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=2'>3</a> with app.app_context():
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=3'>4</a>     table = Pong.query.all()
----> <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=4'>5</a> json_ready = [user.read() for user in table] # "List Comprehensions", for each user add user.read() to list
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=5'>6</a> return json_ready

/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb Cell 14 in <listcomp>(.0)
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=2'>3</a> with app.app_context():
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=3'>4</a>     table = Pong.query.all()
----> <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=4'>5</a> json_ready = [user.read() for user in table] # "List Comprehensions", for each user add user.read() to list
      <a href='vscode-notebook-cell://wsl%2Bubuntu/home/chewyboba10/vscode/sushi-burrito/_notebooks/2023-03-13-AP-unit2-4a-hacks.ipynb#X16sdnNjb2RlLXJlbW90ZQ%3D%3D?line=5'>6</a> return json_ready

AttributeError: 'Pong' object has no attribute 'read'
def update():
    uid = input("What user ID would you like to update?")
    user = find_by_uid(uid) # "List Comprehensions", for each user add user.read() to list
    name = input("Enter desired new name: ")
    uid = input("Enter desired new id: ")
    new_password = input("Enter desired new password: ")
    with app.app_context():
        try:
            object = user.update(name, uid, new_password)   
            print("Updated\n", object.read())
        except:  # error raised if object not created
            print("Unknown error uid {uid}")
    return

update()
Updated
 {'id': 9, 'name': 'goodbye', 'uid': 'bye', 'dob': '12-31-9999', 'age': -7977}
def delete():
    uid = input("What user ID would you like to delete?")
    user = find_by_uid(uid)
    with app.app_context():
        try:
            object = user.delete()  
            print("Deleted\n")
        except:  # error raised if object not created
            print("Unknown error uid {uid}")
    return
    
    db.session.delete(self)
    db.session.commit()
    return None

delete()
Unknown error uid {uid}

Hacks

  • Add this Blog to you own Blogging site. In the Blog add notes and observations on each code cell.
  • Change blog to your own database.
  • Add additional CRUD
    • Add Update functionality to this blog.
    • Add Delete functionality to this blog.