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
    2. SQLAlchemy db object
"""
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:** - db.Model inheritance: db model is being put into the user class which allows us to use the different functions db.Model has**
    • init method: sets up the attributes
    • @property, @<column>.setter: For each attribute there is a property and setter. App property is a getter method and setter is a setter
    • create, read, update, delete methods: CRUD methods. Example: getter method aligns with the read method. These methods are in the userclass.
# import datetime
# from datetime import datetime
# import json

# from sqlalchemy.exc import IntegrityError
# from werkzeug.security import generate_password_hash, check_password_hash


# ''' 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 User(db.Model):
#     __tablename__ = 'users'  # table name is plural, class name is singular

#     # Define the User schema with "vars" from object
#     id = db.Column(db.Integer, primary_key=True)
#     _name = db.Column(db.String(255), unique=False, nullable=False)
#     _uid = db.Column(db.String(255), unique=True, nullable=False)
#     _password = db.Column(db.String(255), unique=False, nullable=False)
#     _dob = db.Column(db.Date)

#     # constructor of a User object, initializes the instance variables within object (self)
#     def __init__(self, name, uid, password="123qwerty", dob=datetime.today()):
#         self._name = name    # variables with self prefix become part of the object, 
#         self._uid = uid
#         self.set_password(password)
#         if isinstance(dob, str):  # not a date type     
#             dob = date=datetime.today()
#         self._dob = dob

#     # 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 uid from object
#     @property
#     def uid(self):
#         return self._uid
    
#     # a setter function, allows uid 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 method used for setter
#     def set_password(self, password):
#         """Create a hashed password."""
#         self._password = generate_password_hash(password, method='sha256')

#     # check password parameter against stored/encrypted password
#     def is_password(self, password):
#         """Check against hashed password."""
#         result = check_password_hash(self._password, password)
#         return result
    
#     # dob property is returned as string, a string represents date outside object
#     @property
#     def dob(self):
#         dob_string = self._dob.strftime('%m-%d-%Y')
#         return dob_string
    
#     # dob setter, verifies date type before it is set or default to today
#     @dob.setter
#     def dob(self, dob):
#         if isinstance(dob, str):  # not a date type     
#             dob = date=datetime.today()
#         self._dob = dob
    
#     # age is calculated field, age is returned according to date of birth
#     @property
#     def age(self):
#         today = datetime.today()
#         return today.year - self._dob.year - ((today.month, today.day) < (self._dob.month, self._dob.day))
    
#     # output content using str(object) is in human readable form
#     # output content using json dumps, this is ready for API response
#     def __str__(self):
#         return json.dumps(self.read())

#     # CRUD create/add a new record to the table
#     # returns self or None on error
#     def create(self):
#         try:
#             # creates a person object from User(db.Model) class, passes initializers
#             db.session.add(self)  # add prepares to persist person object to Users table
#             db.session.commit()  # SqlAlchemy "unit of work pattern" requires a manual commit
#             return self
#         except IntegrityError:
#             db.session.remove()
#             return None

#     # CRUD read converts self to dictionary
#     # returns dictionary
#     def read(self):
#         return {
#             "id": self.id,
#             "name": self.name,
#             "uid": self.uid,
#             "dob": self.dob,
#             "age": self.age,
#         }

#     # CRUD update: updates user name, password, phone
#     # returns self
#     def update(self, name="", uid="", password=""):
#         """only updates values with length"""
#         if len(name) > 0:
#             self.name = name
#         if len(uid) > 0:
#             self.uid = uid
#         if len(password) > 0:
#             self.set_password(password)
#         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:You need to first write db.create_all(). This will create a table. You can then put tester data into the table by creating a variable that contains all the attributes required. you can then run a for loop that runs through the tester data and puts it into the table. 2. User Object Constructors: Takes in inputs and then puts it together using the user() class.
    2. Try / Except: The function first looks at the given condition and if there is an error then the except condition is run.
# # Builds working data for testing
# def initUsers():
#     with app.app_context():
#         """Create database and tables"""
#         db.create_all()
#         """Tester data for table"""
#         u1 = User(name='Thomas Edison', uid='toby', password='123toby', dob=datetime(1847, 2, 11))
#         u2 = User(name='Nikola Tesla', uid='niko', password='123niko')
#         u3 = User(name='Alexander Graham Bell', uid='lex', password='123lex')
#         u4 = User(name='Eli Whitney', uid='whit', password='123whit')
#         u5 = User(name='Indiana Jones', uid='indi', dob=datetime(1920, 10, 21))
#         u6 = User(name='Marion Ravenwood', uid='raven', dob=datetime(1921, 10, 21))


#         users = [u1, u2, u3, u4, u5, u6]

#         """Builds sample user/note(s) data"""
#         for user in users:
#             try:
#                 '''add user to table'''
#                 object = user.create()
#                 print(f"Created new uid {object.uid}")
#             except:  # error raised if object nit created
#                 '''fails with bad or duplicate data'''
#                 print(f"Records exist uid {user.uid}, or error.")
                
# initUsers()

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:This allows you to look through the database and find the specific user with whatever specific attribute you are looking for. 2. user.password: This will allow the user to login or help the developer make sure it is the user trying the login.
# def find_by_uid(uid):
#     with app.app_context():
#         user = User.query.filter_by(_uid=uid).first()
#     return user # returns user object

# # Check credentials by finding user and verify password
# def check_credentials(uid, password):
#     # query email and return user record
#     user = find_by_uid(uid)
#     if user == None:
#         return False
#     if (user.is_password(password)):
#         return True
#     return False
        
# #check_credentials("indi", "123qwerty")

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:This will set user to the filter method. Allows the developer to run the function 'find_by_uid()' whenever user is called. Then the this can be used in the try/except. 2. user = User(...): This sets the variable user to the User() class with the inputted attributes.
    2. user.dob and try/except: This takes the users dob through a manual input and then adds it to the object. if the user didn't input a dob, then the dob is set to the current day.
    3. user.create() and try/except: creates a user in the database if no conditions are violated. If there is an error then the 'except' condition is raised.
# def create():
#     # optimize user time to see if uid exists
#     uid = input("Enter your user id:")
#     user = find_by_uid(uid)
#     try:
#         print("Found\n", user.read())
#         return
#     except:
#         pass # keep going
    
#     # request value that ensure creating valid object
#     name = input("Enter your name:")
#     password = input("Enter your password")
    
#     # Initialize User object before date
#     user = User(name=name, 
#                 uid=uid, 
#                 password=password
#                 )
    
#     # create user.dob, fail with today as dob
#     dob = input("Enter your date of birth 'YYYY-MM-DD'")
#     try:
#         user.dob = datetime.strptime(dob, '%Y-%m-%d').date()
#     except ValueError:
#         user.dob = datetime.today()
#         print(f"Invalid date {dob} require YYYY-mm-dd, date defaulted to {user.dob}")
           
#     # write object to database
#     with app.app_context():
#         try:
#             object = user.create()
#             print("Created\n", object.read())
#         except:  # error raised if object not created
#             print("Unknown error uid {uid}")
        
# create()

Reading users table in sqlite.db

Uses SQLALchemy query.all method to read data

  • Comment on purpose of following
    1. User.query.all:This queries through the database and displays all the data in it. Useful for displaying the the user data on a frontend screen. 2. json_ready assignment, google List Comprehension: This jsonifies the data and then returns it.
# # SQLAlchemy extracts all users from database, turns each user into JSON
# def read():
#     with app.app_context():
#         table = User.query.all()
#     json_ready = [user.read() for user in table] # "List Comprehensions", for each user add user.read() to list
#     return json_ready

# read()

# def patch():
#     uid = input("Enter user id to update:")
    
#     userUpd = User.query.filter_by(_uid=uid).first()
    
#     userUpd.update(uid)

# patch()
    

# def delete():
#     uid = input("Enter user id to delete:")
    
#     userUpd = User.query.filter_by(_uid=uid).first()
    
#     userUpd.delete(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.
""" database dependencies to support sqliteDB examples """
from random import randrange
from datetime import date
import os, base64
import json

from sqlalchemy.exc import IntegrityError
from werkzeug.security import generate_password_hash, check_password_hash


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

# Define the Post class to manage actions in 'posts' table,  with a relationship to 'users' table
class Post(db.Model):
    __tablename__ = 'forumPosts'

    # Define the Notes schema
    id = db.Column(db.Integer, primary_key=True)
    postTitle = db.Column(db.String(255), unique=True, nullable=False)
    post = db.Column(db.String(255), unique=False, nullable=False)
    # Define a relationship in Notes Schema to userID who originates the note, many-to-one (many notes to one user)

    # Constructor of a Notes object, initializes of instance variables within object
    def __init__(self, postTitle, post):
        self.postTitle = postTitle
        self.post = post


    # Returns a string representation of the Notes object, similar to java toString()
    # returns string
    def __repr__(self):
        return "Posts(" + str(self.id) + "," + self.postTitle + "," + self.post + "," + ")"

    # CRUD create, adds a new record to the Notes table
    # returns the object added or None in case of an error
    def create(self):
        try:
            # creates a Notes object from Notes(db.Model) class, passes initializers
            db.session.add(self)  # add prepares to persist person object to Notes table
            db.session.commit()  # SqlAlchemy "unit of work pattern" requires a manual commit
            return self
        except IntegrityError:
            db.session.remove()
            return None

    # CRUD read, returns dictionary representation of Notes object
    # returns dictionary
    def read(self):   
        return {
            "id": self.id,
            "postTitle": self.postTitle,
            "post": self.post
        }

    # CRUD update, updates a record in the Notes table
    def update(self, postTitle="", post=""):
        """only updates values with length"""
        if len(postTitle) > 0:
            self.postTitle = postTitle
        if len(post) > 0:
            self.post = post
        db.session.commit()
        return self

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


# 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



# Builds working data for testing
def initPost():
    with app.app_context():
        """Create database and tables"""
        # db.init_app(app)
        db.create_all()
        #! """Tester data for table"""
        p1 = Post(postTitle='First post', post='This is my first post')
        p2 = Post(postTitle='Second post', post='This is my second post')
        p3 = Post(postTitle='Third post', post='This is my third post')
        
        posts = [p1, p2, p3]

        #! """Builds sample user/note(s) data"""
        for post in posts:
            try:
                '''add a few 1 to 4 notes per user'''
                post.create()
            except IntegrityError:
                '''fails with bad or duplicate data'''
                db.session.remove(posts)
                return None
initPost()
from flask import Blueprint, request, jsonify
from flask_restful import Api, Resource # used for REST API building
from datetime import datetime
from flask import Flask, request, redirect
from flask import Flask
from flask_login import LoginManager
from flask_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate
from flask_cors import CORS

"""
These object can be used throughout project.
1.) Objects from this file can be included in many blueprints
2.) Isolating these object definitions avoids duplication and circular dependencies
"""

# Setup of key Flask object (app)
# app = Flask(__name__)
# CORS(app)
def post():
    with app.app_context():
    # validate 
        postTitle = input("Enter post title: ")
        if postTitle is None or len(postTitle) < 2:
            return {'message': f'post tile is missing, or is less than 2 characters'}, 211
        
        post = input("Enter post: ")
        if post is None or len(post) < 5:
            return {'message': f'please describe your post adequately'}, 212

        ''' #1: Key code block, setup post OBJECT '''
        uo = Post(
                    postTitle=postTitle, 
                    post=post,
                    )

        # create post in database
        uo.create()
        # success returns json of post
        # if createPost:
        #     redirect_url = 'https://unlqsting.github.io/tracker/forum'
        #     return redirect(redirect_url)
        # failure returns error
post()
def get():
    with app.app_context():
        posts = Post.query.all()    # read/extract all posts from database
        json_ready = [createPost.read() for createPost in posts]
        # prepare output in json
        print(json_ready)

get()
        
    # jsonify creates Flask response object, more specific to APIs than json.dumps
[{'id': 1, 'postTitle': 'ofnsekjf', 'post': 'this is the update post'}, {'id': 3, 'postTitle': 'Third post', 'post': 'This is my third post'}, {'id': 4, 'postTitle': 'test post 2 ', 'post': 'this is a test post please just work'}, {'id': 6, 'postTitle': 'First post', 'post': 'This is my first post'}, {'id': 7, 'postTitle': 'Second post', 'post': 'This is my second post'}, {'id': 8, 'postTitle': 'tester post 5', 'post': 'this is the fifth tester post'}, {'id': 9, 'postTitle': 'tester post 8', 'post': 'jdkfahbfhabfwjbh jb ajwb ha'}]
def patch():
    with app.app_context():
    
        lookId = input("Enter the ID of post you want to update: ")
        postTitle = input("Enter new post title: ")
        post = input("Enter new post: ")
        postupd = Post.query.filter_by(id=lookId).first()
        
        
        ''' Avoid garbage in, error checking '''
        
        if postTitle is None or len(postTitle) < 2:
            return {'message': f'post title is missing or is less than 2 characters'}, 211
        
        
        if post is None or len(post) < 12:
            return {'message': f'post is missing or is less than 12 characters'}, 212

        postupd.update(postTitle, post)
patch()
        
def delete():
    ''' Read data for json body '''
    with app.app_context():
    
        lookId = input("Enter the ID of post you want to delete: ")
        postdel = Post.query.filter_by(id=lookId).first()
        
        postdel.delete()
    
delete()