Unit 2.4a Using Programs with Data, SQLAlchemy
Using Programs with Data is focused on SQL and database actions. Part A focuses on SQLAlchemy and an OOP programming style,
- Database and SQLAlchemy
- Hacks
- My Own Database with CRUD
- Defining the Class, setters, getters
- Creating the Database
- Read
- Create
- Update
- Delete
- Schema
- Shopping List Menu
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.
- Flask app object
- app = Flask(name) creates the object
- app is getting assigned the attributes of the object (setter)
- sets up template for database objects
- SQLAlchemy db object
- db = SQLAlchemy()
- sets the database object with properties from SQLAlchemy
- Flask app 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__) # this creates the object and assigns app to the attributes
# 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() # sets the database object with properties from SQUAlchemy
# 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 = defines the template for users (class definition template used to create objects of type user)
- a class is a template for a future object (attributes)
- db.Model inheritance
- inherit ability to make our class use database properties, methods, and attributes
-
init method
- init method helps us instantiate (create) an object from our user class
- instantiates a u1 object with attributes
-
@property
,@<column>.setter
- used to see what is inside an object and is used to change what is inside the object
- setter= allows us to change attributes
- getter (property)= shows us attributes (ex. value name= name is shown)
- used to set and retrieve data
-
create, read, update, delete methods
- methods inside of a class
- interacts with the database
- creates a new user with attributes
- reads a user/s and their attributes
- updates a user and their attributes
- deletes a user and their attributes
-
when executing cell = no result because template is created but data is not instantiated
- class User = defines the template for users (class definition template used to create objects of type user)
""" database dependencies to support sqlite examples """
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): # defines the template for users (class definition template used to create objects of type user)
__tablename__ = 'users' # table name is plural, class name is singular
# Define the User schema with "vars" from object
# able to use db methods, attributes, properties inherritted to add to database template
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)
# init method helps us instantiate (create) an object from our user class
def __init__(self, name, uid, password="123qwerty", dob=datetime.today()): # instantiates a u1 object with attributes
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 # property is a getter that shows us object attributes
def name(self):
return self._name
# a setter function, allows name to be updated after initial object creation
@name.setter # setter function can change the attributes of an object
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): # creates a user in the databse
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): # reads the users in the database and their attributes
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=""): # updates an existing user's attributes
"""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): # deletes a user and their attributes from the database entirely
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?
-
Create All Tables from db Object
Using the SQUAlchemy library, the db.create_all() creates rows for all the objects (u1, u2, u3, u4, u5, and u6) and columns for their attributes.
-
User Object Constructors
The User object constructors are used to add the objects to the class with their corresponding attributes. The "User" before the user data is referring to the User class in which the objects are stored.
-
Try / Except
The try/except is used to check for bad data before a user is added to the class. If the user already exists or the user has bad data, the user will not be added to the class. This helps to prevent future issues when creating the database.
-
"""Database Creation and Testing """
# Builds working data for testing
def initUsers():
with app.app_context():
"""Create database and tables"""
db.create_all() # creates rows for all the objects (u1, u2, u3, u4, u5, and u6) and columns for their attributes.
"""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: # checking for bad data and errors
try:
'''add user to table'''
object = user.create()
print(f"Created new uid {object.uid}")
except: # error raised if object not 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
-
User.query.filter_by
- Object Relational Model = ORM
- identifies credentials
- searches through the class to find what is defined in the parameter
- this returns the first match in class (or if it exists)
-
user.password
- checks to see if passwords match
- if not found = will return false
- if found = will return true
-
def find_by_uid(uid):
with app.app_context():
user = User.query.filter_by(_uid=uid).first() # identifies credentials, will return true/false
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)): # checks to see if passwords match
return True # is yes= returns true
return False # if no- returns 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
- user.find_by_uid() and try/except
- This tests to see if a user already exists. If a user already exists, it will return the user's data, but won't create a new one. If the user does not already exist, then the program will continue.
- user = User(...)
- user = User() defines user as a variable with the attributes from the class of user objects.
- user.dob and try/except
- This line of code is to avoid garbage data with the date. If the user inputs in incorrect date formatting, the program will not be able to figure out the age and will need to return an error.
- user.create() and try/except
- This line of code is to check if an object is successfully created or not. If a user is successfully created, it will return a success message. If not, the code will return an error message along with the corresponding uid.
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() # checks for correct formatting and calculates age
except ValueError:
user.dob = datetime.today()
print(f"Invalid date {dob} require YYYY-mm-dd, date defaulted to {user.dob}") # prints an error for incorrect format- this avoids garbage data with the date
# write object to database
with app.app_context():
try:
object = user.create()
print("Created\n", object.read()) # the program will try to create a user and if it fails, it will move to except
except: # error raised if object not created
print("Unknown error uid {uid}") # will print an error if error with user creation
create()
Reading users table in sqlite.db
Uses SQLALchemy query.all method to read data
-
Comment on purpose of following
- User.query.all
- User.query.all requests all of the user objects from the user class and database. This line is defining "table" as all of this data.
- json_ready assignment, google List Comprehension
- List comprehension = a shorter syntax to edit existing lists (one line versus three or four)
- the json_ready line uses list comprehension to create a new list of all the user data in the database in json format.
# SQLAlchemy extracts all users from database, turns each user into JSON
def read():
with app.app_context():
table = User.query.all() # defines table as request of all of the user objects from the user class
json_ready = [user.read() for user in table] # "List Comprehension", for each user add user.read() to list
return json_ready
read()
class shopping_list(db.Model): # defines the template for shopping list
__tablename__ = 'Shopping_List'
# Define the shopping schema with "vars" from object
# able to use db methods, attributes, properties inherritted to add to database template
id = db.Column(db.Integer, primary_key=True)
_item = db.Column(db.String(255), unique=True, nullable=False)
_storename = db.Column(db.String(255), unique=False, nullable=False)
_price = db.Column(db.Integer, unique=False, nullable=False)
_quantity = db.Column(db.Integer, unique=False, nullable=False)
# constructor of an item object, initializes the instance variables within object (self)
# init method helps us instantiate (create) an object from class
def __init__(self, item, storename, price, quantity): # instantiates an item
self._item = item # variables with self prefix become part of the object,
self._storename = storename
self._price = price
self._quantity = quantity
# an item getter method, extracts item from object
@property # property is a getter that shows us object attributes
def item(self):
return self._item
# a setter function, allows item to be updated after initial object creation
@item.setter # setter function can change the attributes of an object
def item(self, item):
self._item = item
# a getter method, extracts storename from object
@property
def storename(self):
return self._storename
# a setter function, allows storename to be updated after initial object creation
@storename.setter
def storename(self, storename):
self._storename = storename
# a getter method, extracts uid from object
@property
def price(self):
return self._price
# a setter function, allows uid to be updated after initial object creation
@price.setter
def price(self, price):
self._price = price
# a getter method, extracts uid from object
@property
def quantity(self):
return self._quantity
# a setter function, allows uid to be updated after initial object creation
@quantity.setter
def quantity(self, quantity):
self._quantity = quantity
# 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): # creates an item in the database
try:
# creates an item object from shopping_list(db.Model) class, passes initializers
db.session.add(self) # add prepares to persist item object to Shopping_list 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): # reads the users in the database and their attributes
return {
"id": self.id,
"item": self.item,
"storename": self.storename,
"price": self.price,
"quantity": self.quantity
}
# CRUD update: updates item, storename, price, quantity
# returns self
def update(self, item="N/A", storename="N/A", price="N/A", quantity="N/A"): # updates an existing item's attributes
"""only updates values with length"""
if len(item) > 0:
self.name = item
if len(storename) > 0:
self.uid = storename
if len(price) > 0:
self.price = price
if len(quantity) > 0:
self.quantity = quantity
db.session.commit()
return self
# CRUD delete: remove self
# None
def delete(self): # deletes an item and their attributes from the database entirely
db.session.delete(self)
db.session.commit()
return None
"""Database Creation and Testing """
# Builds working data for testing
def initShopping_list():
with app.app_context():
"""Create database and tables"""
db.create_all() # creates rows for all the objects (u1, u2, u3, u4, u5, and u6) and columns for their attributes.
"""Tester data for table"""
i1 = shopping_list(item='Gummies', storename='Sprouts', price='3', quantity='4')
i2 = shopping_list(item='Milk', storename='Vons', price='10', quantity='2')
i3 = shopping_list(item='Oreos', storename='Costco', price='5', quantity='1')
i4 = shopping_list(item='Ham', storename='Sprouts', price='15', quantity='1')
i5 = shopping_list(item='Gatorade', storename='Vons', price='12', quantity='3')
i6 = shopping_list(item='Cheez-its', storename='Vons', price='6', quantity='10')
items = [i1, i2, i3, i4, i5, i6]
"""Builds sample item data"""
for item in items: # checking for bad data and errors
try:
'''add item to table'''
object = item.create()
print(f"Created new item: {object.item}")
except: # error raised if object not created
'''fails with bad data'''
print("error, bad data")
initShopping_list()
def read_list():
with app.app_context():
shoppingtable = shopping_list.query.all() # defines table as request of all of the user objects from the user class
json_ready = [item.read() for item in shoppingtable] # "List Comprehension", for each user add user.read() to list
return json_ready
read_list()
def create_item():
# request value that ensure creating valid object
item = input("Enter item:")
storename = input("Enter the storename:")
price = input("Enter the price:")
quantity = input("Enter the quantity:")
# Initialize item object before date
item = shopping_list(item=item,
storename=storename,
price=price,
quantity=quantity
)
# write object to database
with app.app_context():
try:
object = item.create()
print("Created\n", object.read()) # the program will try to create an item and if it fails, it will move to except
except: # error raised if object not created
print("Unknown error item {item}") # will print an error if error with user creation
create_item()
def update_item():
updateditem = input("What item would you like to update?")
with app.app_context():
item = shopping_list.query.filter_by(_item=updateditem).first()
if item is None:
print("Item not found")
else:
new_item = input("Enter new item name (press enter to cancel): ")
new_storename = input("Enter new store name (press enter to cancel): ")
new_price = input("Enter new price (press enter to cancel): ")
new_quantity = input("Enter new quantity (press enter to cancel): ")
if new_item:
item.item = new_item
if new_storename:
item.storename = new_storename
if new_price:
item.price = new_price
if new_quantity:
item.quantity = new_quantity
db.session.commit()
print("Updated item:", item)
update_item()
def delete_item():
item = input("What item would you like to delete?")
with app.app_context():
result = shopping_list.query.filter_by(_item=item).first()
if result is None:
print("Item not found")
else:
result.delete()
print("Deleted item:", result._item)
delete_item()
import sqlite3
database = 'instance/sqlite.db' # this is location of database
def schema_item():
# Connect to the database file
conn = sqlite3.connect(database)
# Create a cursor object to execute SQL queries
cursor = conn.cursor()
# Fetch results of Schema
results = cursor.execute("PRAGMA table_info('Shopping_list')").fetchall()
# Print the results
for row in results:
print(row)
# Close the database connection
conn.close()
schema_item()
def item_menu():
operation = input("Enter: (C)reate (R)ead (U)pdate or (D)elete or (S)chema")
if operation.lower() == 'c':
create_item()
elif operation.lower() == 'r':
shoppinglist = read_list()
print(shoppinglist)
elif operation.lower() == 'u':
update_item()
elif operation.lower() == 'd':
delete_item()
elif operation.lower() == 's':
schema_item()
elif len(operation)==0: # Escape Key
return
else:
print("Please enter c, r, u, or d")
item_menu() # recursion, repeat menu
try:
item_menu() # start menu
except:
print("Perform Jupyter 'Run All' prior to starting menu")