Effortless REST with Flask, Part 2

Connecting a Database

heroku config:get DATABASE_URL -a {your_heroku_app_name}

Connecting to the Database

Defining Models

"User Model"
# pylint: disable=no-member
from app import db
from sqlalchemy.sql import func

# A generic user model that might be used by an app powered by flask-praetorian
class User(db.Model):
__tablename__ = "users"

user_id = db.Column(db.Integer, primary_key=True)
username = db.Column(db.String(255), unique=True)
password = db.Column(db.String(255))
roles = db.Column(db.Text)
is_active = db.Column(db.Boolean, default=True, server_default="true")
created_datetime = db.Column(
db.DateTime(), nullable=False, server_default=func.now()
)

@property
def rolenames(self):
try:
return self.roles.split(",")
except Exception:
return []

@classmethod
def lookup(cls, username: str):
return cls.query.filter_by(username=username).one_or_none()

@classmethod
def identify(cls, user_id: int):
return cls.query.get(user_id)

@property
def identity(self):
return self.user_id

def is_valid(self):
return self.is_active

def __repr__(self):
return f"<User {self.user_id} - {self.username}>"

Seeding the Database

invoke init-db
invoke seed-db

Using SQLAlchemy Models to Query the Database

Authentication and Authorization

"Main Flask App"
...
# Application Factory
# https://flask.palletsprojects.com/en/1.1.x/patterns/appfactories/
def create_app(config_name: str) -> Flask:
"""Create the Flask application
Args:
config_name (str): Config name mapping to Config Class
Returns:
[Flask]: Flask Application
"""
from app.config import config_by_name
from app.models import User
# Create the app
app = Flask(__name__)
# Log the current config name being used and setup app with the config
app.logger.debug(f"CONFIG NAME: {config_name}")
config = config_by_name[config_name]
app.config.from_object(config)
# Initialize the database
db.init_app(app)
# Initialize the flask-praetorian instance for the app
guard.init_app(app, User)

@app.route("/")
def hello_world() -> str: # pylint: disable=unused-variable
return "Hello World!"

@app.route("/login", methods=["POST"])
def login():
# Ignore the mimetype and always try to parse JSON.
req = request.get_json(force=True)
username = req.get("username", None)
password = req.get("password", None)
user = guard.authenticate(username, password)
ret = {"access_token": guard.encode_jwt_token(user)}
return (jsonify(ret), 200)

@app.route("/users", methods=["GET"])
@auth_required
def users():
users = User.query.all()
return jsonify(UserSchema(many=True).dump(users))

@app.route("/users/admin-only", methods=["GET"])
@roles_required("admin")
def users_admin():
users = User.query.all()
return jsonify(UserSchemaWithPassword(many=True).dump(users))

return app

--

--

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store