-
Notifications
You must be signed in to change notification settings - Fork 61
Flask
#Flask: a micro web development framework
The Flask micro-web framework for Python and Twitter’s Bootstrap is a really easy way to build an API on a web-server. This setup assumes you’ve already instantiated a web server on AWS - if not, refer to the AWS setup Dev.
We highly recommend you follow the section in the AWS Dev on creating your own free t2.micro instance (not on Insight's AWS account) so you can keep the website up after Insight officially ends (You should used a Ubuntu 14.04 server with EBS). This will also give you a chance to get familiar with AWS in case you want to use it in the future.
Alternatively, you can run this on your local machine and use localhost instead of an IP.
##Preparing your instance
It's good to update any new instance by issuing the following:
node:~$ sudo apt-get update
node:~$ sudo apt-get upgrade
Then install Python’s library import tool pip and flask with:
node:~$ sudo apt-get install python-pip python-dev build-essential
Now, install Flask
node:~$ sudo pip install flask
Go to the directory where you want have your Flask app and then create the following directories
node:~$ mkdir app
node:~$ mkdir app/static
node:~$ mkdir app/templates
node:~$ mkdir tmp
The app
folder will be where we will put our application package. The static
sub-folder is where we will store static files like images, javascripts, and stylesheets. The templates
sub-folder is where our templates (html) files will go.
Make a new file called app/__init__.py
with the following content:
node:~$ nano app/__init__.py
Add the following code to it:
from flask import Flask
app = Flask(__name__)
from app import views
The script above simply creates the application object (of class Flask
) and then imports the views
module, which we haven't written yet.
The views are the handlers that respond to requests from web browsers. In Flask, views are written as Python functions. Each view
function is mapped to one or more request URLs.
Let's write our first view function in a new file called app/views.py
:
node:~$ nano app/views.py
Add the following code to it
from app import app
@app.route('/')
@app.route('/index')
def index():
return "Hello, World!"
This view is actually pretty simple; it just returns a string, to be displayed on the client's web browser. The two route decorators above the function create the mappings from urls /
and /index
to this function.
The final step to have a fully working web app is to create a script that starts up the development web server with our application. Let's make a new file called run.py
(in the folder a level above app
):
node:~$ nano run.py
Add the following code to it
#!/usr/bin/env python
from app import app
app.run(host='0.0.0.0', debug = True)
The script simply imports the app
variable from our app
package and invokes its run method to start the server. Remember that the app variable holds the Flask instance we created above.
Your directory structure should be as follows:
app\
static\
templates\
__init__.py
views.py
tmp\
run.py
To start the app you just make this script executable and then run it.
node:~$ chmod +x run.py
node:~$ ./run.py
After the server initializes it will listen on port 5000 waiting for connections. You can check your Flask on http://node-public-dns:5000
or http://node-public-dns:5000/index
if you have your Flask app on an EC2 node. If you are just testing Flask app on your laptop, you can check the results on http://localhost:5000
or http://localhost:5000/index
.
The first URL maps to /
, while the second maps to /index
. Both routes are associated to our view function, so they produce the same result. If you enter any other route you will get an error, since only these two have been mapped to a view function. When you are done playing with the server you can just hit Ctrl-C in the Terminal to stop the web server.
#Tornado Server
Tornado is a scalable, non-blocking web server and web application framework written in Python. This would help your Flask apps to handle multiple users without crashing it. You can read more about it here. Below is how you can set it to work with your Flask app.
node:~$ sudo pip install tornado
node:~$ nano tornadoapp.py
#! /usr/bin/env python
from tornado.wsgi import WSGIContainer
from tornado.ioloop import IOLoop
from tornado.web import FallbackHandler, RequestHandler, Application
from app import app
class MainHandler(RequestHandler):
def get(self):
self.write("This message comes from Tornado ^_^")
tr = WSGIContainer(app)
application = Application([
(r"/tornado", MainHandler),
(r".*", FallbackHandler, dict(fallback=tr)),
])
if __name__ == "__main__":
application.listen(80)
IOLoop.instance().start()
Now your directory structure would look like this:
app\
static\
templates\
__init__.py
views.py
tmp\
run.py
tornadoapp.py
You can now run your web app as follows:
node:~$ sudo -E python tornadoapp.py
And your app will be running at the following url - http://<instance-public-ip>
(you don’t need the port 5000 here and you need to use sudo otherwise it will throw permission error)
#Creating an API
##Query and display data from Cassandra
Install Python Cassandra driver:
node:~$ sudo pip install cassandra-driver
Let’s query the Cassandra table we created in the Cassandra Dev.
Edit your app/views.py
to be following:
# jsonify creates a json representation of the response
from flask import jsonify
from app import app
# importing Cassandra modules from the driver we just installed
from cassandra.cluster import Cluster
# Setting up connections to cassandra
# Change the bolded text to your seed node public dns (no < or > symbols but keep quotations. Be careful to copy quotations as it might copy it as a special character and throw an error. Just delete the quotations and type them in and it should be fine. Also delete this comment line
cluster = Cluster(['<seed-node-public-dns>'])
# Change the bolded text to the keyspace which has the table you want to query. Same as above for < or > and quotations. Also delete this comment line
session = cluster.connect('<keyspace>')
@app.route('/')
@app.route('/index')
def index():
return "Hello, World!"
@app.route('/api/<email>/<date>')
def get_email(email, date):
stmt = "SELECT * FROM email WHERE id=%s and date=%s"
response = session.execute(stmt, parameters=[email, date])
response_list = []
for val in response:
response_list.append(val)
jsonresponse = [{"first name": x.fname, "last name": x.lname, "id": x.id, "message": x.message, "time": x.time} for x in response_list]
return jsonify(emails=jsonresponse)
Now, restart your web server:
node:~$ sudo -E python tornadoapp.py
If you now go to http://node-public-dns/api/david@insightdataengineering.com/2015-09-01
, you will see the json response with emails from david@insightdataengineering.com
on 2015-09-01
. Using this concept, you can create an api for your own application.
##Flask Templates
Let's write our first template in a new file app/templates/index.html
:
<html>
<head>
<title>{{title}} - microblog</title>
</head>
<body>
<h1>Hello, {{user.nickname}}!</h1>
</body>
</html>
We just wrote a mostly standard HTML page, with the only difference that there are some placeholders for the dynamic content enclosed in {{ ... }} sections.
Now let's edit app/views.py
to demonstrate this. Add this to the top:
from flask import render_template
Now find the index
module and replace its functionality with:
def index():
user = { 'nickname': 'Miguel' } # fake user
return render_template("index.html", title = 'Home', user = user)
Try the application at this point to see how the template works.
node:~$ sudo -E python tornadoapp.py
Go to http://node-public-dns
and you will see -- Hello Miguel!
Once you have the rendered page in your browser you may want to view the source HTML and compare it against the original template.
To render the template we had to import a new function from the Flask framework called render_template
. This function takes a template name and a variable list of template arguments and returns the rendered template, with all the arguments replaced.
Under the covers, the render_template
function invokes the Jinja2 templating engine that is part of the Flask framework. Jinja2 substitutes {{...}} blocks with the corresponding values provided as template arguments.
##Control Statements in Templates
Sometimes, if no data is passed in render_template
then you can have a default text to be displayed by using if
statements in your HTML template:
Let’s edit our app/templates/index.html
to the following:
<html>
<head>
<title>{{title}} - microblog</title>
</head>
<body>
{% if user %}
<h1>Hello, {{user.nickname}}!</h1>
{% else %}
<h1>Hello, Insight Fellow!</h1>
{% endif %}
</body>
</html>
The if statements basically checks if the user variable has been passed or not. Now, let’s edit our index
function in app/views.py
to the following:
def index():
user = { 'nickname': 'Miguel' } # fake user
return render_template("index.html", title = 'Home')
You can run the application and see the difference. Now, try passing the user variable as before and reload the page.
##Loops in Templates
Many a times you will have a list that you would want to iterate through and print its elements in some form on the webpage. Let’s see an example of how to do that. Assuming we have a list of numbers we want to display on our webpage. Go ahead and edit the index function app/views.py
script as follows
def index():
user = { 'nickname': 'Miguel' } # fake user
mylist = [1,2,3,4]
return render_template("index.html", title = 'Home', user = user, mylist = mylist)
Now, we are sending an array to our template. Edit app/templates/index.html
similar to below to render all the elements on the webpage.
<html>
<head>
<title>{{title}} - microblog</title>
</head>
<body>
{% if user %}
<h1>Hello, {{user.nickname}}!</h1>
{% else %}
<h1>Hello, Insight Fellow!</h1>
{% endif %}
<br />
<ul>
{% for num in mylist %}
<li>{{num}}</li>
{% endfor %}
</ul>
</body>
</html>
Download Bootstrap:
node:~$ wget https://github.com/twbs/bootstrap/releases/download/v3.3.7/bootstrap-3.3.7-dist.zip
node:~$ sudo apt-get install unzip
When you extract the file, you’ll find the directories: css, fonts, and js:
node:~$ unzip bootstrap-3.3.7-dist.zip
Copy these directories into your app/static folder.
node:~$ cp -R bootstrap-3.3.7-dist/* app/static
Let’s create an html template in the app/templates
folder. This time, we’re going to use Twitter Bootstrap to make stuff pretty. Let’s use the Starter Template. Check out this set of templates and select the Starter Template.
View the page source (right-click in Chrome for example) and [copy that] (http://getbootstrap.com/examples/starter-template/) into a new file called app/templates/base.html
on your node.
Copy the contents at this link into a file named app/static/css/starter-template.css
Edit the following lines in base.html
so that the path to all CSS and JS files is correct:
node:~$ sudo nano app/templates/base.html
Change this line:
<link href="../../dist/css/bootstrap.min.css" rel="stylesheet">
to:
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
Change this line:
<link href="starter-template.css" rel="stylesheet">
to:
<link href="../static/css/starter-template.css" rel="stylesheet">
Change this line:
<script src="../../dist/js/bootstrap.min.js"></script>
to:
<script src="../static/js/bootstrap.min.js"></script>
Let’s edit the app/views.py
file to create a new view for this template. Add following lines to app/views.py
:
@app.route('/email')
def email():
return render_template("base.html")
Now, run the application (sudo -E python tornadoapp.py
or refresh the page if the application is already running) and go to http://node-public-dns/email
.
For most web app, the headers, navigation bars and footers stay the same. So, instead of creating a template for every page from scratch, we can inherit this base.html.
Let’s create a Flask webpage that displays the data that we fetched from Cassandra table on the email.html
template, and instead of an API, let’s have a textbox with a button to type in queries. For this example, make a copy of base.html
and name it mybase.html
Now, let’s create a new template to have a textbox with a button:
node:~$ nano app/templates/email.html
Add the following lines to the html file
{% extends "mybase.html" %} <!--this means that you are extending the base tempate -->
{% block emailid %} <!-- this is the name of the block below -->
<div class="row" style="height:100vh;">
<div class="col-md-4 col-md-offset-4" style="margin-top:10vh;">
<form action="email" method="POST">
<!-- action is the html template that will return the result to this input query -->
<div class="form-group text-center">
<label style="font-weight:300; font-size:36px;">Type in an email id</label>
<br /><br />
<input type="text" class="form-control" id="emailid" name="emailid" placeholder="abc@example.com">
<select class="form-control" name="date">
<option>2015-09-01</option>
<option>2015-09-02</option>
<option>2015-09-03</option>
</select>
<br /><br />
<button type="submit" value="Send" name="emailid-container" class="btn btn-default id-submit">Submit</button>
</div>
</form>
</div>
</div>
{% endblock %}
Now, let’s edit the email view in app/views.py
as follows :
@app.route('/email')
def email():
return render_template("email.html")
@app.route("/email", methods=['POST'])
def email_post():
emailid = request.form["emailid"]
date = request.form["date"]
#email entered is in emailid and date selected in dropdown is in date variable respectively
stmt = "SELECT * FROM email WHERE id=%s and date=%s"
response = session.execute(stmt, parameters=[emailid, date])
response_list = []
for val in response:
response_list.append(val)
jsonresponse = [{"fname": x.fname, "lname": x.lname, "id": x.id, "message": x.message, "time": x.time} for x in response_list]
return render_template("emailop.html", output=jsonresponse)
Also in app/views.py
, add this near the top:
from flask import request
or, alternatively add the request
module to a prior import statement instead:
from flask import render_template, request
We will now create emailop.html
to display the results:
node:~$ nano app/templates/emailop.html
Add the following code to the file:
{% extends "mybase.html" %}
{% block emailop %}
<div class="container">
<div class="starter-template">
<div class="row" style="height:100vh;">
<div class="col-md-10 col-md-offset-1">
<table class="table">
<thead>
<tr>
<th>id</th>
<th>time</th>
<th>fname</th>
<th>lname</th>
<th>message</th>
</tr>
</thead>
<tbody>
{% for val in output %}
<tr>
<td>{{val.id}}</td>
<td>{{val.time}}</td>
<td>{{val.fname}}</td>
<td>{{val.lname}}</td>
<td>{{val.message}}</td>
</tr>
{% endfor %}
</tbody>
</table>
</div>
</div>
</div>
</div>
{% endblock %}
Now edit app/templates/mybase.html
in this section:
…
</nav>
<div class="container">
<div class="starter-template">
<h1>Bootstrap starter template</h1>
<p class="lead">Use this document as a way to quickly start any new project.<br> All you get is this text and a mostly barebones HTML document.</p>
</div>
</div><!-- /.container -->
Make the following changes to that section in order to extend mybase.html
and add the two new elements to the template:
…
</nav>
<div class="container">
<div class="starter-template">
<h1>My new app</h1>
{% block emailid %}{% endblock %}
{% block emailop %}{% endblock %}
</div>
</div><!-- /.container -->
…
To test these changes, go to the url: http://node-public-dns/email
and in the textbox, type david@insightdataengineering.com and choose the first date option.
#Using JQuery/AJAX for a Live Updating Site
You can make any Javascript function refresh live with Asynchronous Javascript (and XML), also known as AJAX. For example, create a new route in your app/views.py
. For a page that changes in real-time add this to your views.py
:
@app.route('/realtime')
def realtime():
return render_template("realtime.html")
Then create a realtime.html in the templates directory and edit it:
node:~$ nano app/templates/realtime.html
Add the following code to it
<html>
<body>
<p id="title">UTC Clock</p>
<p id="clock">Current Time</p>
</body>
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js">
</script>
<script>
function anyFunction()
{
function fixTimeFormat(i) {
return ( i < 10 ) ? "0" + i : i;
}
var today = new Date(),
h = fixTimeFormat(today.getHours()),
m = fixTimeFormat(today.getMinutes()),
s = fixTimeFormat(today.getSeconds());
document.getElementById("clock").innerHTML = h + ":" + m + ":" + s;
}
$(function () {
setInterval(anyFunction, 2000);
});
</script>
</html>
Now if you start your Flask server and go to your website, http://node-public-dns/realtime
, you’ll see the clock updating automatically every 2 seconds.
##How it works
This HTML loads the AJAX and JQuery libraries with the line:
<script src="http://ajax.googleapis.com/ajax/libs/jquery/1.11.0/jquery.min.js"></script>
Other than that, there is a general function, anyFunction
, that displays the current UTC time using the built-in Javascript Date
object and a function to fix the formatting. This uses JQuery
to edit the HTML in the paragraph with the id “clock”.
The important part that updates automatically with AJAX (which you can recognize from the $ preceding the function) is:
$(function () {
setInterval(anyFunction, 2000);
});
which simply calls anyFunction every 2000ms. Any function (or several functions) can be inserted here. In particular, this function can fetch results from an API endpoint with the latest data and display it on a graph.
##GitHub Repo Some of the code used in this dev can be found in this repository: https://github.com/InsightDataScience/flask-dev
Find out more about the Insight Data Engineering Fellows Program in New York and Silicon Valley, apply today, or sign up for program updates.
You can also read our engineering blog here.