Part II: The Flask DOM controller and RESTFUL API

Setting up the database: Part I

In this post we will both put the skeleton of our site together, and put the meat on these bones. So, it will be a bit of work. We'll first build a simple API and make sure that it works before connecting it to the database setup in the last entry. To do this, we'll employ the Flask-RESTFUL package.

Again, this is not a traditional CRUD API (i.e. allowing for Creation, Reading, Updating, and Deleting of entries in our database.), because we'll only be reading. However, the work to add the other functionality is not hard, and there are many tutorials available for setting it up, if you'd like to do that.

Setup

When using Flask, keep in mind that it expects the pages it renders to be located in a templates subfolder

$ mkdir -p someApp/templates
$ cd someApp/templates
$ touch index.html
$ cd ..
$ touch app.py

While this is an app we'll be deploying to Heroku, the ability to test it locally will be extremely helpful. Additionally, the easiest way to deploy to Heroku is via git. The following code will setup a git repository in our app directory, and a virtual environment so that we don't disturb the local package eco-system. While this is not necessary, it is good practice. If you already have virtual environment set up on your machine, don't worry about the first line. After it is setup in our app directory, we'll need to install all the packages that are needed. As far as git goes, it's not necessary that you connect your working directory to a github repository, but, again, in practice this is an excellent idea since you can always revert to a previous commit if you accidentally break your app. We'll get further into this later on with a development branch, but you can choose to do this or skip it. If you do choose to connect with github, create a new repo and make note of its url on the Quick Setup page to which you're redirected after creation, .

$ sudo apt-get install virtualenv
$ cd someApp
$ virtualenv -p /usr/bin/python2.7 venv-2.7
$ source venv-2.7/bin/activate
$ pip install flask  flask-sqlalchemy marshmallow-jsonapi marshmallow psycopg2 flask-restful
$ git init
$ git add .
$ git commit -m 'inital commit'
$ git remote add <repo url>
$ git remote -v
$ git push origin master

*note, you don't have to add the virtual environment to your repo. simply add app.py and index.html individually. Upon your initial commit, you'll be notified that the venv-2.7 folder in your working directory is not staged for a commit. This is exactly as planned. After pushing, you'll want to create a file that tells git to ignore this subdirectory:

$ cd someApp
$ touch .gitignore
$ nano .gitignore

In the editor, add to this file the line venv-2.7 and Ctrl-X to prompt the option to save and exit.

We'll build upon a very sparse app. This initial iteration will merely render our index page template.

from flask import Flask, render_template

app = Flask(__name__)

@app.route('/')
def index():
    return render_template('index.html')


if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port, debug=True)

Add the following lines to templates/index.html:

<!doctype html>
<html ng-app='myApp'>
<meta charset="utf-8">
<head>
    <!-- CSS -->

    <!-- JS --> 
</head>
<div id='main'>
    Index Page
</div>
</html>

Let's test our app! In your terminal, the following code and right-click open the link provided.

$ python app.py

Now, let's create the framework for our API. These are the packages we'll be using, and how they'll benefit us.

web app - flask_sqlalchemy

  • Documentation
  • Summary - db.Model is a base class of SQLAlchemy with a built-in query attribute that facilitates querying of the model. Additional functionality for defining a true CRUD controller must be user defined attributes of the model--which, again--we won't get into.

  • How to use - define a class for your table, with variables for each column and fixed data type. Additionally, we have to actually connect our database to our Flask app. The way we'll do this is by setting an environmental variable to the login info that was chosen for the database, and then we'll configure the app with this information. Heroku makes this easy by allowing the user to specify local variables for each app, but locally we'll have to do something a little more involved. This also provides a way of accessing your db and posting your code to github without publicizing your database login credentials.

In a terminal, navigate to the app directory. Create a file named .env and enter the following lines, with your login info:

source env/bin/activate
export DATABASE_URL="postgresql://<USERNAME>:<PASSWORD>@<ENDPOINT>/<DB_NAME>?user=<USERNAME>&password=<PASSWORD>"

Where the ENDPOINT can be found on your Amazon RDS Dashboard, and should look something like <DB_INSTANCE>.co0kbuzosniz.us-west-1.rds.amazonaws.com:5432.

Add .env to your .gitignore file on the next line, so that this file does not get pushed to the repository, should you choose to create one. Next, we make sure this variable is declared every time we enter the app directory. Run the following in your terminal to update then refresh your .bashrc:

$ echo "source `which activate.sh`" >> ~/.bashrc
$ source ~/.bashrc

Now we'll define the db.Model subclass we talked about above. The lines below can be added to your current app.py.

from flask_sqlalchemy import SQLAlchemy
import os 

app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']
db = SQLAlchemy(app)

class Routes(db.Model):

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=True, nullable=False)
    length_in_meters = db.Column(db.Float, nullable=False)
    elevation_gain_in_meters = db.Column(db.Float, nullable=False)
    start_lat = db.Column(db.Float, nullable=False)
    start_lon = db.Column(db.Float, nullable=False)
    end_lat = db.Column(db.Float, nullable=False)
    end_lon = db.Column(db.Float, nullable=False)
    route_type = db.Column(db.Integer, nullable=False)
    sub_type = db.Column(db.Integer, nullable=False)
    popularity = db.Column(db.Float, nullable=False)

Schema - marshmallow_jsonapi

  • Documentation
  • Summary - marshmallow is a tool for converting complex datatypes, such as objects, to and from native Python datatypes. Here, it allows us to deserialize and validate the data coming from the database.

  • How to use - we define a Schema in much the same way we construct the SQL table and the db.Model for SQLAlchemy, with columns this time being a field object. In the definition of our get method of the flask_restful api resource class associated to database table, we use the dump attribute of the schema we define to serialize the query data.

from marshmallow_jsonapi import Schema, fields

class RouteSchema(Schema):

    not_blank = validate.Length(min=1, error='Field cannot be blank')
    # add validate=not_blank in required fields
    id = fields.Integer(required=True)
    name = fields.String(required=True)
    length_in_meters = fields.Float(required=True)
    elevation_gain_in_meters = fields.Float(required=True)
    start_lat = fields.Float(required=True)
    start_lon = fields.Float(required=True)
    end_lat = fields.Float(required=True)
    end_lon = fields.Float(required=True)
    route_type = fields.Integer(required=True)
    sub_type = fields.Integer(required=True)
    popularity = fields.Float(required=True)

Api - flask_restful

  • Documentation
  • Summary - Resources provide the main functionality of this package. They are, as far as I can tell, a wrapper for the standard CRUD HTTP methods. flask_restful also constructs the api endpoints by which we access information through the DOM.

  • How to use - Resources are defined as a class inheriting from the Resource class imported from the flask_restful package. In it you will define the methods which you wish to access. In our get method, we'll call both the query method of our db.Model class and the dump method of our Schema class. We'll also have to define an Api object and add our resource to it, but we'll wait to do this in the next next step.

from flask_restful import Api, Resource

class CreateListRoutes(Resource):
    
    def get(self):
        routes_query = Routes.query.limit(5)
        results = schema.dump(routes_query, many=True).data
        return results

blueprint - Flask

  • Documentation
  • Summary - Just to be clear, Blueprint is way overkill for this application. With that out of the way, Flask Blueprint is a tool for compartmentalizing your website on the flask level. A blueprint defines a collection of views, templates, static files and other elements that can be applied to an application. Blueprints provide a way of scaling, and easy implementation of functionality that may be useful later on.

  • How to use - we'll define our blueprint with a name, import_name (__name__), template_folder, and static_folder. Then we'll pass the blueprint on to define the flask_restful.Api, add the resource from the previous step to it, and register the blueprint with our app and specify a url_prefix.

from flask import Blueprint

api_v1 = Blueprint('api', __name__)
api = Api(api_v1)

api.add_resource(CreateListRoutes, '.json')
app.register_blueprint(api_v1, url_prefix='/api/v1/routes')

In the end, your app.py should look like this:

from flask import Flask, Blueprint, render_template
from marshmallow_jsonapi import Schema, fields
from flask_restful import Api, Resource
from flask_sqlalchemy import SQLAlchemy
import os

app = Flask(__name__)
app.config['SQLALCHEMY_DATABASE_URI'] = os.environ['DATABASE_URL']

db = SQLAlchemy(app)


class Routes(db.Model):

    id = db.Column(db.Integer, primary_key=True)
    name = db.Column(db.String, unique=True, nullable=False)
    length_in_meters = db.Column(db.Float, nullable=False)
    elevation_gain_in_meters = db.Column(db.Float, nullable=False)
    start_lat = db.Column(db.Float, nullable=False)
    start_lon = db.Column(db.Float, nullable=False)
    end_lat = db.Column(db.Float, nullable=False)
    end_lon = db.Column(db.Float, nullable=False)
    route_type = db.Column(db.Integer, nullable=False)
    sub_type = db.Column(db.Integer, nullable=False)
    popularity = db.Column(db.Float, nullable=False)

 
class RouteSchema(Schema):

    not_blank = validate.Length(min=1, error='Field cannot be blank')
    # add validate=not_blank in required fields for creating new entries
    id = fields.Integer(required=True)
    name = fields.String(required=True)
    length_in_meters = fields.Float(required=True)
    elevation_gain_in_meters = fields.Float(required=True)
    start_lat = fields.Float(required=True)
    start_lon = fields.Float(required=True)
    end_lat = fields.Float(required=True)
    end_lon = fields.Float(required=True)
    route_type = fields.Integer(required=True)
    sub_type = fields.Integer(required=True)
    popularity = fields.Float(required=True)



# Initialize a Flask Blueprint,
api_v1 = Blueprint('api', __name__)
# Initialize the  API object using the Flask-RESTful API class
api = Api(api_v1)


# Initialize the UserSchema we defined in models.py
schema = RouteSchema(strict=True)


 
# Create CRUD classes using the Flask-RESTful Resource class
class CreateListRoutes(Resource):
    
    def get(self):
        routes_query = Routes.query.limit(5)
        results = schema.dump(routes_query, many=True).data
        #return results['data']
        return results


# Map classes to API enspoints
api.add_resource(CreateListRoutes, '.json')
app.register_blueprint(api_v1, url_prefix='/api/v1/routes')



@app.route('/')
def index():
    return render_template('index.html')




if __name__ == '__main__':
    port = int(os.environ.get('PORT', 5000))
    app.run(host='0.0.0.0', port=port, debug=True)