Focus on deploying a simple Flask Application into Heroku, interacting with PostgreSQL and Troubleshooting
By: Xue Wang
Tags: Postgres, Flask, Heroku

Focus on deploying a simple Flask Application into Heroku, interacting with PostgreSQL and Troubleshooting

Flask is the tool that can be used to create API server. It is a micro-framework, which means that its core functionality is kept simple, but there are numerous extensions to allow developers to add other functionality (such as authentication and database support).

Heroku is a cloud platform where developers host applications, databases, and other services in several languages. Developers can use Heroku to deploy, manage, and scale applications. Heroku is also free, with paid specialized memberships, and most services such as a database offer a free tier.

Remark:

This story will focus on application deployment and database interactive without frontend and authentication. A tested sample application will be provided, so you don’t need to test it locally.

Let’s start.

First, the dependency: is based on my local setting as below:

python 3.8 (or higher than python3) PostgreSQL client and server are installed in Windows Ubuntu (Windows subsystem): Ubuntu 20.04.2 LTS PostgreSQL client and server are installed in Ubuntu Heroku CLI

Prepared Flask application sample

You can use the below sample for the deployment.

Before we start, create a database in PostgreSQL, give the name, no further action needed.

create databse local_db_name;

Create below 6 files that are needed for the application

models.py which defines the PostgreSQL database, table, and the configuration

import os

from sqlalchemy import Column, String, Integer, create_engine

from flask_sqlalchemy import SQLAlchemy db = SQLAlchemy()

'''

setup_db(app):

binds a flask application and a SQLAlchemy service

'''

def setup_db(app): database_name ='local_db_name'

default_database_path= "postgres://{}:{}@{}/{}".format('postgres', 'password', 'localhost:5432', database_name)

database_path = os.getenv('DATABASE_URL', default_database_path)

app.config["SQLALCHEMY_DATABASE_URI"] = database_path

app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False

db.app = app

db.init_app(app) '''

drops the database tables and starts fresh

can be used to initialize a clean database

''' def db_drop_and_create_all():

db.drop_all()

db.create_all() class Movie(db.Model):

__tablename__ = 'movies'

id = Column(Integer, primary_key=True)

title = Column(String(80), unique=True)

release_date = Column(db.DateTime) def __init__(self, title, release_date):

self.title = title

self.release_date = release_date def details(self):

return {

'id': self.id,

'title': self.title,

'release_date': self.release_date,

} def insert(self):

db.session.add(self)

db.session.commit() def delete(self):

db.session.delete(self)

db.session.commit() def update(self):

db.session.commit()

There are two database paths in models.py:

default_data_path is the local PostgreSQL path for local test

DATABASE_URL is the Heroku database URL, which will be generated with Heroku command and saved in setup.sh file

With os.getenv(), if DATABASE_URL is empty, it will get default_data_path directly

2. app.py: the main application

import os

from flask import Flask, request, abort, jsonify

from flask_cors import CORS

from models import setup_db, Movie, db_drop_and_create_all def create_app(test_config=None):

# create and configure the app

app = Flask(__name__)

setup_db(app)

CORS(app) """ uncomment at the first time running the app """

db_drop_and_create_all() @app.route('/', methods=['GET'])

def home():

return jsonify({'message': 'Hello,hello, World!'}) @app.route("/movies")

def get_movies():

try:

movies = Movie.query.order_by(Movie.release_date).all()

movie=[]

movie=[mov.release_date for mov in movies]

return jsonify(

{

"success": True,

"movie name": movie

}

), 200

except:

abort(500) @app.errorhandler(500)

def server_error(error):

return jsonify({

"success": False,

"error": 500,

"message": "server error"

}), 500 return app app = create_app() if __name__ == '__main__':

port = int(os.environ.get("PORT",5000))

app.run(host='127.0.0.1',port=port,debug=True)

3. manage.py: which can manage the database schema and changes that you make to it. With this, Heroku can run the migrations to the database hosted on the platform.

from flask_script import Manager

from flask_migrate import Migrate, MigrateCommand from app import app

from models import db migrate = Migrate(app, db)

manager = Manager(app) manager.add_command('db', MigrateCommand) if __name__ == '__main__':

manager.run()

4. Procfile file: specifies the commands that are executed by the app on startup.

web: gunicorn app:app

5. requirements.txt file to include all of the dependencies.

angles==2.0

certifi==2020.4.5.1

chardet==3.0.4

Flask==1.1.2

Flask-Cors==3.0.8

Flask-Migrate==2.5.3

Flask-Script==2.0.6

Flask-SQLAlchemy==2.4.1

Jinja2==2.11.2

jose==1.0.0

Mako==1.1.2

MarkupSafe==1.1.1

psycopg2-binary==2.8.5

pyasn1==0.4.8

python-dateutil==2.8.1

python-editor==1.0.4

python-jose==3.1.0

requests==2.23.0

rsa==4.0

six==1.14.0

SQLAlchemy==1.3.16

urllib3==1.25.9

Werkzeug==1.0.1

xacro==1.13.3

gunicorn==20.0.4

boto==2.49.0

botocore==1.16.5

If the dependency not being installed yet, use the below to install locally. This is for the local run. For deploying in Heroku, Heroku will search remotely, not from your local end.

pip install -r requirements.txt

6. And we also need to create setup.sh file to save the environmental variable (DATABASE_URL), currently add one line and let it empty.

export DATABASE_URL = ''

Now the files should look like this:

Run local migrations

For migration, run the below commands:

python3 manage.py db init

python3 manage.py db migrate

python3 manage.py db upgrade

We can run our local migrations using our file, to mirror how Heroku will run behind the scenes for us when we deploy our application. In a local development environment, if there is any change in the database, I would like to use both migrate and upgrade commands, but in the production environment, in Heroku, migrate should be used at your local end, and then upgrade in Heroku.

Now the files should look like below:

The folders migrations and __pycache__ are generated automatically.

Now check the below folder:

migrations->versions

If it is empty, create an empty file called ‘.keep’ (or another name with the dot) as below:

If the version is empty, there will have an error during running git push command

manage.run()

...... FileNotFoundError: [Errno 2] No such file or directory: '/app/migrations/versions'

Deploying to Heroku

Supposed that Heroku CLI has been installed. Below commands are run in Ubuntu.

start to run Heroku commands

heroku login

It will prompt Heroku login page, click login in (supposed you have already signed up before), Ubuntu should automatically log you in and prompt another command line

2. Create Heroku app with the name casting-agency-xw

heroku create casting-agency-xw

3. Create a PostgreSQL database in Heroku: the only parameter you need to give is the application name. The database will be generated automatically with this command.

heroku addons:create heroku-postgresql:hobby-dev --app casting-agency-xw

4. Check the config

heroku config --app casting-agency-xw

You will find the DATABASE_URL as below:

=== casting-agency-xw Config Vars DATABASE_URL: postgres://boozofewcboexi:fXXXXXX

Fix configuration on Heroku

Come back to the application setup.sh file, copy the DATABASE_URL from the previous step into setup.sh file as below

export DATABASE_URL='postgres://boozofewcboexi:fXXXXXX'

Deploy now

You can come to the below page in Heroku-deploy, using Heroku Git, as below codes, which is a bit different from the process from the website.

git init

heroku git:clone -a casting-agency-xw

git add .

git commit -am "add new files"

git push heroku master

If everything is fine, the application is deployed as below:

But if you go to resource, click the Heroku-database, it will show 0 tables. Next, we need to migrate the database.

Migration the database

Run below command:

heroku run python3 manage.py db upgrade --app casting-agency-xw

You will see the table movies being added to the database, but it is empty. Next, let’s add a record to it.

Interactive with the database through Heroku psql

Make sure not only the client but also the server also being installed.

If try: psql version, the result is like below:

psql (PostgreSQL) 12.6 (Ubuntu 12.6-0ubuntu0.20.04.1)

but running psql return the error like below, it might be that the server hasn’t been installed.

psql: error: could not connect to server: No such file or directory

Is the server running locally and accepting

connections on Unix domain socket "/var/run/postgresql/.s.PGSQL.5432"?

This link in StackOverflow might help you to install it.

Once the server is installed correctly, it should look like this:

Password:

Welcome to Ubuntu 20.04.2 LTS (GNU/Linux 4.4.0-18362-Microsoft x86_64)

postgres=#

Now connect to the Heroku database using the below command:

heroku pg:psql postgresql-clear-05212 --app casting-agency-xw

postgresql-clear-05212 is the Heroku database name

Now insert two records:

Let’s check the endpoints:

Open Endpoint:

The get movies endpoint: /movies, it looks like:

Now delete one record with id =1:

The endpoint will look like this:

The result is the same as what we expect.

Tipps:

At first, deployment, uncomment db_drop_and_create_all() to generate the tables, but if you have made changes and want to git push the changes, do comment this command, as it will conflict with existing tables in Heroku.

""" uncomment at the first time running the app """

db_drop_and_create_all()

2. If the deployment is ok, the database also works, but the error code 503, one possibility is that it might have previous traffic conflicted. Use below to scale dyno

heroku ps:scale web=0

Wait for a while (maybe 30 seconds), then try below:

heroku ps:scale web=1

2. If you need to update the table, run db migrate locally and upgrade on Heroku.

3. Understanding the table generated process:

init: create the alembic _versoin table, which is used internally by Alembic

migrate: check models.py and compare that to what is in your database actually.

The deployment might be various according to the local setting.

Thank you for your reading.

Reference: