Web Development With Python and Flask
Web Development With Python and Flask
Web Development With Python and Flask
Table of contents
This book is for
Table of contents
PREFACE
Chapter 1 - Preparation
Chapter 2 - Getting your hands dirty with Hello World!
Chapter 3 - Setting up your development environment
Chapter 4 - Making the app look good.
Chapter 5 - Databases - made simple
What well build next:
Chapter 6 - Making the app tick.
Chapter 7 - Forms
Chapter 8 - User login - with management
Chapter 9 - An Admin Panel to save us all
Chapter 10 - Prepare for the production environment
Features
Chapter 11 - Going online
Chapter 15 - A list of addons that might interest you
Flask-Babel - translate your website easy
Flask-Cache - Adds cache support to your Flask application.
Flask-Login - Flask-Login provides user session management for Flask. It
handles the common tasks of logging in, logging out, and remembering your
users' sessions over extended periods of time.
Flask-MongoAlchemy - Add Flask support for MongoDB using MongoAlchemy
Flask-OAuth - guess what it does :)
Flask-OpenID
Flask-Testing - The Flask-Testing extension provides unit testing utilities for
Flask.
Flask-Uploads - Flask-Uploads allows your application to flexibly and efficiently
handle file uploading and serving the uploaded files.
If the answer to all above is YES then go ahead. If you dont know, then you really
should start with some basic python. There are lots of nice and free resources on net.
It helps if you know a little html too, like What does <h1> do ? for example.
Disclaimer:
This book is written by an amateur, and its goal is to provide you just with a
starting point into Python - Flask web programming and giving you my own version on
how to do things.
An amateur (French amateur "lover of", from Old French and ultimately from Latin
amatorem nom. amator, "lover") is generally considered a person attached to a particular
pursuit, study, or science in a non-professional or unpaid manner. Amateurs often have
little or no formal training in their pursuits, and many are autodidacts (self-taught).
(wikipedia)
PREFACE
Flask is minimal and simple. You dont get ORMs, Admin Panels and other stuff that
Django has out of the box. You can install a very cool admin panel with just 1 line of
code: pip install flask-admin and integrate it with 3-4 lines in your app.
It is easy to learn, powerful and combined with Tornado it produces awesome
performance even on a small VPS of 1Ghz.
write in http://www.google.com/trends/ Flask Python
What you see is the trending of Flask Programming. Pretty cool isnt it ?
As extra well discuss in the last chapter, best practices for production environment and
optimizing it for a high traffic app.
Chapter 1 - Preparation
You can skip this chapter if you already have a linux environment set up and you dont
what to setup a new one.
Suggestion:
For $5 per month, I strongly suggest that you get a VPS at digitalocean.com. In fact I will
give you a gift of $10 if you sign up now using this link (youll receive the money in your
account after you sign in and get a new droplet) http://goo.gl/ArLvyx (this is a referal link
to me from DigitalOcean, this way you can say thank you for this book and get $10)
- you get a static ip, you can add very easy a domain name and its very fast. Now
(August 2014) you can choose locations from: New York, San Francisco, Amsterdam,
Singapore, London.
lets get started:
1. Install Ubuntu latest version
2. Login into ssh
This book assumes that you are familiar with terminal commands and running remote
commands with putty. If you need help read this article
https://www.digitalocean.com/community/tutorials/how-to-log-into-your-droplet-withputty-for-windows-users
Tip: if you use Windows, install Putty then install https://code.google.com/p/superputty/
and you have a nice putty managment with multiple windows.
Commands to run:
df -h
ifconfig a
ping c 4 google.com
* if you get unknown host * -> sudo nano /etc/resolv.conf nameserver 192.168.1.1 (line
down) nameserver 8.8.8.8
sudo apt-get update && sudo apt-get upgrade
[ssh]
sudo apt-get install -y openssh-server
sudo nano /etc/ssh/sshd_config
sudo restart ssh
[failtoban]
sudo apt-get install -y fail2ban
sudo nano /etc/fail2ban/jail.conf
check that you have this configuration
[ssh]
enabled = true
port = ssh
filter = sshd
logpath = /var/log/auth.log
maxretry = 3
sudo /etc/init.d/fail2ban restart
sudo fail2ban-client status
sudo apt-get install -y build-essential python python-dev python-pip pythonmysqldb libmysqlclient-dev supervisor libmemcached-dev memcached
python-memcache
Nginx (pronounced "engine-x") is an open source reverse proxy server for HTTP,
HTTPS, SMTP, POP3, and IMAP protocols, as well as a load balancer, HTTP cache,
and a web server (origin server). The nginx project started with a strong focus on high
concurrency, high performance and low memory usage.
Webmin is a web-based system configuration tool for Unix-like systems, although recent
versions can also be installed and run on Windows. With it, it is possible to configure
operating system internals, such as users, disk quotas, services or configuration files, as
well as modify and control open source apps, such as the Apache HTTP Server, PHP or
MySQL.
pip is a package management system used to install and manage software packages
written in Python.
Tornado is a scalable, non-blocking web server and web application framework written
in Python.
Tornado is noted for its high performance. It tries to solve the C10k problem affecting
other servers. The following table shows a benchmark test of Tornado against other
Python-based servers:
Server
Setup
Tornado
8213
3353
Django
Apache/mod_wsgi
2223
web.py
Apache/mod_wsgi
2066
CherryPy
Standalone
785
nano run.py
run.py
from flask import Flask
app = Flask(__name__)
@app.route('/')
def hello_world():
return 'Hello World!'
if __name__ == '__main__':
app.run()
save it, CTRL+X then Y, Enter
now type:
python run.py
http://your_server_ip:5000/
If you forgot your server ip, write
wget -qO- http://ipecho.net/plain ; echo
Or if you use digitalocean you can see it after you login on your account after your VPS
name.
You should see a white page with a Hello World!. Thats all.
After you are done admiring your first flask application, hit CTRL+C.
PRO TIP OF THE DAY: you can write on top of run.py #!/usr/bin/python then chmod +x
run.py so you can just type ./run.py instead of python run.py.
If you get error like: -bash: ./run.py: /usr/bin/python^M: bad interpreter: No such file or
directory
apt-get install dos2unix then write dos2unix run.py. [and configure your IDE to use Line
separator Unix and OSX. code style->general in IntelliJ]
If you already knew this, and I offended you with this time wasting info, I apologize!
SuperPuty: https://code.google.com/p/superputty/
Start IntelliJ, configure whatever it asks you, choose New Project -> Python -> (dont
check Django, Google App Engine etc) just click next. next. Python interpreter. Name
your project.
Skip this step if you plan to use your own computer for development. But
eventually youll end up doing this in the end.
Go to Tools -> Deployment -> Configuration -> Add a new server. Select SFTP, fill the
detals host, port, username, password. Click test SFTP connection. You should see an
Successfully message. Select root path. Create a new folder in /home (for example).
Click the next tab from top Mappings
Type / in the Deployment path on the server server_name
Click Use this server as default (a button on top)
Go to Tools -> Deployment -> Options. Check Create empty directories. And select
Upload changed files automatically to the default server Always.
Now, to have IntelliJ show you nicely the code you need to install the packages on your
computer too.
You need to install them both on windows and on the server because IntelliJ will use
them too.
mkdir flask_tutorial
cd flask_tutorial
mkdir app
mkdir app/static
mkdir app/templates
In the main directory flask_tutorial create a file named run.py
Inside app create a __init__.py and views.py
The app folder is containing the bread and butter. Static folder is for css, js, jpg etc. files.
The __init__.py is where we will create our app object. The run.py is where the server
will be.
Edit: app/__init__.py
from flask import Flask
# Define the WSGI application object
app = Flask(__name__)
from app import views
Here we just create the app object and import the views in it. In views we keep all the
logic on how the app responds to url requests.
Edit: app/run.py
import tornado
from tornado import autoreload
from tornado.wsgi import WSGIContainer
from tornado.httpserver import HTTPServer
from tornado.ioloop import IOLoop
from tornado.log import enable_pretty_logging
from app import app
enable_pretty_logging()
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(1337)
ioloop = tornado.ioloop.IOLoop().instance()
autoreload.start(ioloop)
ioloop.start()
This is a default configuration on 1 core for the tornado server. Well start like this
because youll just write it once and youll use it until the end of the book.
If you want to go into details with tornado, at the end of the book Ill give you a better
config, but for now this is more than enough.
Pretty logging for a nice display on the terminal. Notice that we start the app on port
1337!
[offtopic]
If 1337 doesnt tell you anything then heres the wikipedia intro to it.
Leet (or "1337"), also known as eleet or leetspeak, is an alternative alphabet for the
English language that is used primarily on the Internet. It uses various combinations of
ASCII characters to replace Latinate letters. For example, leet spellings of the word leet
include 1337 and l33t; eleet may be spelled 31337 or 3l33t.
The term leet is derived from the word elite. The leet alphabet is a specialized form of
symbolic writing. Leet may also be considered a substitution cipher, although many
dialects or linguistic varieties exist in different online communities. The term leet is also
used as an adjective to describe formidable prowess or accomplishment, especially in
the fields of online gaming and in its original usage computer hacking.
[/offtopic]
Edit: app/views.py
from app import app
@app.route('/')
@app.route('/index')
def index():
<link href="//netdna.bootstrapcdn.com/bootstrap/
{{ bootstrap_version }}/css/bootstrap.min.css" rel="stylesheet" />
<link href="//netdna.bootstrapcdn.com/bootswatch/
{{ bootswatch_version }}/{{ bootswatch_theme }}/bootstrap.min.css"
rel="stylesheet" >
Pro tip of the day: instead of the bootswatch - slate theme, replace it with the
one of your liking.
You notice some tags like
{% include 'includes/nav.html' %}
As you probably suspect in the templates folder create another folder named includes.
We will use it to structure our code in a nice way, adding navigation to our site much
more easy.
The {{ variable }} means dynamic content. Something like: Total users: <?php
$total_users; ?> in php
/app/templates/includes/nav.html
<nav class="navbar navbar-default" role="navigation">
<div class="navbar-header">
<button type="button" class="navbar-toggle" data-toggle="collapse"
data-target="#bs-example-navbar-collapse-1">
<span class="sr-only">Toggle navigation</span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Test Flask</a>
<ul class="nav navbar-nav navbar-right">
<li><a href="#">Example Nav</a></li>
</ul>
</div>
</nav>
Because well be using after some time a nice way to give feedback to users called flash
lets create it now
/app/templates/includes/flash_message
{% with messages = get_flashed_messages(with_categories=true) %}
{% if messages %}
{% if category == 'error' %}
{% set icon = 'icon-exclamation-sign' %}
{% elif category == 'success' %}
{% set icon = 'icon-ok-sign' %}
{% else %}
In the static folder create 3 folders: css, img, js. In the folder css create a file
named main.css and in the js one main.js. You can use the to add custom
things to your template.
This is all! Now we have everything in place. Just need to tell that on / or /index url
the view should display the index.html template. To do so update
/app/views.py
from app import app
SQLAlchemy is the Python SQL toolkit and Object Relational Mapper that gives
application developers the full power and flexibility of SQL.
An ORM is good for abstracting the datastore (sqlite, mysql, oracle, etc etc) in order to
provide an interface that can be used in your code.
If you didnt installed it from the first chapter go ahead and write
pip install flask-sqlalchemy
Because well use lots of configuration constants in our app. Lets organize them in a
config file.
In the main folder flask_tutorial, (where the run.py is) create a new file called
config.py
/config.py
import os
basedir = os.path.abspath(os.path.dirname(__file__))
class Config(object):
DEBUG = False
TESTING = False
SQLALCHEMY_DATABASE_URI = ''
APP_NAME = 'Flask Test'
SECRET_KEY = 'thisisaveryhardsecret!1234!1234'
class ProductionConfig(Config):
SQLALCHEMY_DATABASE_URI =
'mysql://username:password@server_ip/db'
DEBUG = False
class DevelopmentConfig(Config):
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir,
'db.sqlite')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
DEBUG = True
class TestingConfig(Config):
SQLALCHEMY_DATABASE_URI = 'sqlite:///' + os.path.join(basedir,
'db.sqlite')
SQLALCHEMY_MIGRATE_REPO = os.path.join(basedir, 'db_repository')
TESTING = True
Now we should let know that the app will use this configuration
/app/__init__.py
from flask import Flask
from flask.ext.sqlalchemy import SQLAlchemy
import logging
app = Flask(__name__)
app.config.from_object('config.DevelopmentConfig')
db = SQLAlchemy(app)
logger = logging.getLogger(__name__)
logger.setLevel(logging.INFO)
Now a model for the database is required, which are a collection of classes that well use
to interact with the db.
A database model is a type of data model that determines the logical structure of a
database and fundamentally determines in which manner data can be stored, organized,
and manipulated. The most popular example of a database model is the relational
model, which uses a table-based format. [wikipedia]
create a model.py in /app
/app/model.py
import datetime
from app import db
class Tracking(db.Model):
__tablename__ = "tracking"
id = db.Column(db.Integer, primary_key=True)
user_ip = db.Column(db.String(46))
user_agent = db.Column(db.String(100))
at_time = db.Column(db.DateTime, default=datetime.datetime.now)
def add_data(self, user_ip, user_agent):
new_user = Tracking(user_ip=user_ip, user_agent=user_agent)
db.session.add(new_user)
db.session.commit()
def list_all_users(self):
return Tracking.query.all()
def __repr__(self):
return '<Tracking %r>' % (self.id)
Update views.py to
/app/views.py
from flask import render_template, request
from models import *
from flask.ext.admin import Admin, BaseView, expose
The
@app.before_first_request
This creates the database when you first access your website.
You can test the database with a quick view: nano db.sqlite .you should see your table
there.
Using request we get the visitor ip and the user-agent and we store it each time we
refresh the page.
Open your browser http://server_ip:1337/
And python run.py
You should see in the terminal something like this
[I 140827 11:22:29 views:18] 192.168.0.123 Mozilla/5.0 (Windows NT 6.3; WOW64)
AppleWebKit/537.36 (KHTML, like Gecko) Chrome/36.0.1985.143 Safari/537.36
and each time you refresh it, more records are added.
This means that if you write you_website.com/welcome you will get the
welcome.html from the templates directory.
You can also use the routing to display static content like this:
@app.route("/favicon.ico")
def favicon():
return app.send_static_file("img/favicon.ico")
or other static content:
see http://stackoverflow.com/a/14054039/1031297
from flask import Flask, request, send_from_directory
@app.route('/robots.txt')
@app.route('/sitemap.xml')
def static_from_root():
return send_from_directory(app.static_folder, request.path[1:])
You can also make the url dynamic and add variables to it like this (from flask
documentation)
@app.route('/user/<username>')
def show_user_profile(username):
# show the user profile for that user
return 'User %s' % username
Or with an integer
@app.route('/post/<int:post_id>')
def show_post(post_id):
# show the post with the given id, the id is an integer
return 'Post %d' % post_id
You can combine them
@app.route('/hello/')
@app.route('/hello/<name>')
def hello(name=None):
return render_template('hello.html', passed_name=name)
Notice the variable name with the default None. You can use it in your templates using {{
}}
like <p>Greeting {{ passed_name }}!</p>
Now lets modify the index.html template to show nicely the records in our database and
add a new url to display a record by id.
/app/views.py
from flask import render_template, request
from app import *
from models import *
@app.route('/')
@app.route('/index')
def index():
new_tracking = Tracking()
new_tracking.add_data(request.remote_addr, request.headers.get('User-
Agent'))
list_records = new_tracking.list_all_users()
return render_template("index.html", list_records=list_records)
PRO TIP OF THE DAY: You can use CTRL+ALT+L to re-format your code in IntelliJ. Try
it.
The return Tracking.query.all() returns a list with all records. We passed the list of all
records to our index template.
{% else %}
<li><em>no users found</em></li>
{% endfor %}
</ul>
[great examples taken from jinja2 documentation]
Test it in your browser. Works! but wait, it looks ugly. Lets wrap it up in a bootstrap nice
table.
{% extends "base.html" %}
{% block content %}
<div style="font-size: 1.5em;text-align: center">
<h3>Test Flask</h3>
<hr>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>IP</th>
<th>User Agent</th>
</tr>
</thead>
{% for record in list_records %}
<tr>
<td> {{ record.user_ip }}</td>
<td>{{ record.user_agent }}</td>
</tr>
{% endfor %}
</table>
</div>
{% endblock content %}
/app/views.py
from flask import render_template, request
from app import *
from models import *
@app.route('/')
@app.route('/index')
@app.route('/index/<int:page>')
def index(page=1):
new_tracking = Tracking()
new_tracking.add_data(request.remote_addr, request.headers.get('User-Agent'))
list_records =
new_tracking.list_all_users(page,app.config['LISTINGS_PER_PAGE'])
return render_template("index.html", list_records=list_records)
Now we have to add the pagination 1, 2, 3.8,9 small buttons at the bottom of the page.
/app/templates/index.html
{% extends "base.html" %}
{% block content %}
<div style="font-size: 1.5em;text-align: center">
<h3>Test Flask</h3>
<hr>
<table class="table table-striped table-bordered table-hover">
<thead>
<tr>
<th>At Time</th>
<th>IP</th>
<th>User Agent</th>
</tr>
</thead>
{% for record in list_records.items %}
<tr>
<td> {{ record.at_time }}</td>
<td> {{ record.user_ip }}</td>
<td>{{ record.user_agent }}</td>
</tr>
{% endfor %}
</table>
<ul class="pagination">
{%- for page in list_records.iter_pages() %}
{% if page %}
{% if page != list_records.page %}
<li> <a href="{{ url_for('index', page = page) }}">{{ page }}</a> </li>
{% else %}
<li class="active"> <a href="#"><strong>{{ page }}</strong></a>
</li>
{% endif %}
{% else %}
<li> <span class=ellipsis></span> </li>
{% endif %}
{%- endfor %}
</ul>
</div>
{% endblock content %}
Optional Improvements:
add a time formatting:
<td> {{ record.at_time.strftime('%Y-%m-%d %H:%M:%S') }}</td>
order the records by created time:
/app/models.py
[...]
from sqlalchemy import asc, desc
[...]
def list_all_users(self,page, LISTINGS_PER_PAGE):
return
Tracking.query.order_by(desc(Tracking.at_time)).paginate(page,
LISTINGS_PER_PAGE, False)
Chapter 7 - Forms
Lets create some forms to add new records to our database.
create a new file /app/forms.py
/app/forms.py
from app import *
from wtforms.validators import Required, Length
from wtforms import Form, TextField
class TrackingInfoForm(Form):
user_ip = TextField('user_ip', validators=[Required(), Length(max=46,
message='max 46 characters')])
user_agent = TextField('user_agent', validators=[Length(max=46, message='max
46 characters')])
We are using WTF Forms. You can play with them like adding different fields, validators. Buts
lets keep it basic for now.
Were importing logging to do some debuging in the app, the forms and flash to display some
feedback to the user.
<span class="icon-bar"></span>
<span class="icon-bar"></span>
</button>
<a class="navbar-brand" href="/">Test Flask</a>
<ul class="nav navbar-nav navbar-right">
<li><a href="/add_record">Add record</a></li>
</ul>
</div>
</nav>
Test it by adding few records to the db. You should also see in the terminal the debug
message.
The most important part of this chapter are 2 homeworks that you really should do. They are
simple and should take 2-3 minutes for the first one and about 10 minutes for the second.
Homework 1.
Add a validator for ip address in the form.
HINT: You should search the documentation of WTFForms
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
Homework 2.
Remember the track_user_ip to display a record filtered by ip ? Add to the track_ip.html page a
form with one input text where you can enter the IP and a button submit. Display the filtered ip
page after.
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
[ spoiler alert ]
add
/app/forms.py
class QueryOneForm(Form):
user_ip = TextField('user_ip', validators=[Required(), IPAddress(message="Invalid
IP Address")])
/app/templates/includes/nav.html
<li><a href="/track">Query record</a></li>
/app/views.py
new_tracking = Tracking()
list_records = new_tracking.track_user_ip(user_ip, page,
app.config['LISTINGS_PER_PAGE'])
return render_template("track_ip.html", list_records=list_records, form=form,
user_ip = user_ip)
and
/app/templates/track_ip.html
{% extends "base.html" %}
{% block content %}
<h3>Track IP</h3>
<hr>
<div class="row">
<div class="form-group">
<div class="col-lg-4 col-lg-offset-4">
<ul class="pagination">
{%- for page in list_records.iter_pages() %}
{% if page %}
{% if page != list_records.page %}
<li> <a href="{{ url_for('track_user_ip', user_ip = user_ip, page =
page) }}">{{ page }}</a> </li>
{% else %}
<li class="active"> <a
href="#"><strong>{{ page }}</strong></a> </li>
{% endif %}
{% else %}
<li> <span class=ellipsis></span> </li>
{% endif %}
{%- endfor %}
</ul>
</div>
{% endblock content %}
MAIL_USE_TLS = False
MAIL_USERNAME = 'email@yahoo.com'
MAIL_PASSWORD = 'password'
DEFAULT_MAIL_SENDER = 'email@yahoo.com'
SECURITY_EMAIL_SENDER = 'email@yahoo.com'
we need to add the flask-mail object
/app/__init__.py
[...]
from flask_mail import Mail
[...]
mail = Mail(app)
https://github.com/mrjoes/flask-admin
Flask-Admin is a batteries-included, simple-to-use Flask extension that lets you add
admin interfaces to Flask applications. It is inspired by the django-admin package, but
implemented in such a way that the developer has total control of the look, feel and
functionality of the resulting application. (official description)
Working with flask admin is very simple. Heres the way to do it with SQLAlchemy.
from flask.ext.admin.contrib.sqla import ModelView
# Flask and Flask-SQLAlchemy initialization here
admin = Admin(app)
admin.add_view(ModelView(User, db.session))
And this is all! It creates an admin panel for the User, with all the goodies that come with
it.
However, we want to customize it and to protect it with flask-security.
return current_user.has_role('admin')
So heres the code for our app. Theres no need to copy it, just read it line by line and try
to understand it
class TrackingAdminView(ModelView):
can_create = True
def is_accessible(self):
return current_user.has_role('end-user')
def __init__(self, session, **kwargs):
super(TrackingAdminView, self).__init__(Tracking, session, **kwargs)
class UserAdminView(ModelView):
column_exclude_list = ('password')
def is_accessible(self):
return current_user.has_role('admin')
def __init__(self, session, **kwargs):
super(UserAdminView, self).__init__(User, session, **kwargs)
class RoleView(ModelView):
def is_accessible(self):
return current_user.has_role('admin')
def __init__(self, session, **kwargs):
super(RoleView, self).__init__(Role, session, **kwargs)
admin = Admin(app, name="Flask Test Admin")
admin.add_view(TrackingAdminView(db.session))
admin.add_view(UserAdminView(db.session))
admin.add_view(RoleView(db.session))
In order to tune the app for performance, lets first make it do some hard work. Go into config
and modify the LISTINGS_PER_PAGE = 500
Now for the testing lets use apache benchmark. The idea is to use it from a different machine
than the one you are hosting the app. If you have 2 servers just use another one, if you want to
have it on windows
Go to http://www.apachehaus.com/cgi-bin/download.plx and download Apache 2.4.10 x64 (you
have a x64 OS dont you ?). Inside /bin you find ab.exe. Run it with PowerShell or cmd just like on linux
Testing with:
/run.py
http_server = HTTPServer(WSGIContainer(app))
http_server.listen(1337)
ioloop = tornado.ioloop.IOLoop().instance()
autoreload.start(ioloop)
ioloop.start()
Improvements list
/run.py
http_server = HTTPServer(WSGIContainer(app))
http_server.bind(1337)
http_server.start(0)
ioloop = tornado.ioloop.IOLoop().instance()
autoreload.start(ioloop)
ioloop.start()
Changing the config to Production, with a mysql server hosted on another machine. Make sure
the Debug is set to False
4 core tornado:
Requests per second: 13.39 [#/sec] (mean)
Time per request:
3735.012 [ms] (mean)
Time per request:
74.700 [ms] (mean, across all concurrent requests)
Alright, now were talking. With multithread and mysql the performance skyrocketed.
$sudo apt-get install nginx
nginx + 4 core tornado + mysql
Requests per second: 13.45 [#/sec] (mean)
Time per request:
3716.845 [ms] (mean)
Time per request:
74.337 [ms] (mean, across all concurrent requests)
server {
listen 80 default;
server_name domain.com;
server_name www.domain.com;
access_log /var/log/nginx/domain.com.access.log;
root /home/your_flask_project;
location /static/ {
expires max;
add_header Last-Modified $sent_http_Expires;
alias /home/your_flask_project/app/static/;
location / {
try_files $uri @tornado;
}
location @tornado {
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_pass
http://127.0.0.1:1337;
}
}
Now you should change the port on your testing machine.
.\ab.exe -n 100 -c 50 http://server_ip/ (nginx server is on another port)
Want more testing ? check out http://www.slashroot.in/httperf-web-server-performancetest
Make the app autorestart on crash:
apt-get install supervisor
Supervisor:
(from the official website)
Features
Simple
Supervisor is configured through a simple INI-style config file thats easy to learn. It
provides many per-process options that make your life easier like restarting failed
processes and automatic log rotation.
Centralized
Supervisor provides you with one place to start, stop, and monitor your processes.
Processes can be controlled individually or in groups. You can configure Supervisor to
provide a local or remote command line and web interface.
Efficient
Supervisor starts its subprocesses via fork/exec and subprocesses dont daemonize.
The operating system signals Supervisor immediately when a process terminates, unlike
some solutions that rely on troublesome PID files and periodic polling to restart failed
processes.
Extensible
Supervisor has a simple event notification protocol that programs written in any
language can use to monitor it, and an XML-RPC interface for control. It is also built with
extension points that can be leveraged by Python developers.
Compatible
Supervisor works on just about everything except for Windows. It is tested and
supported on Linux, Mac OS X, Solaris, and FreeBSD. It is written entirely in Python, so
installation does not require a C compiler.
Proven
While Supervisor is very actively developed today, it is not new software. Supervisor has
been around for years and is already in use on many servers.
Note that: Supervisor will not run at all under any version of Windows.
lets now insert our program into supervisor. Terminate the app if you have it running and
/etc/supervisor/supervisord.conf
The [include] section can just contain the "files" setting. This
setting can list multiple files (separated by whitespace or
newlines). It can also contain wildcards. The filenames are
interpreted as relative to this file. Included files *cannot*
include files themselves.
[include]
files = /etc/supervisor/conf.d/*.conf
[program:mysuperapp]
command=python /home/the_path_to_your_project/run.py
stderr_logfile = /var/log/supervisor/mysuperapp-stderr.log
stdout_logfile = /var/log/supervisor/mysuperapp-stdout.log
autostart=true
autorestart=true
stdout_logfile_maxbytes=10MB
stderr_logfile_maxbytes=10MB
startsecs=5
startretries=20
Example:
Now login in your droplet, install supervisor, install nginx, deploy your super flask app
etc.
Flask-Login - Flask-Login provides user session management for Flask. It handles the
common tasks of logging in, logging out, and remembering your users' sessions over
extended periods of time.
Flask-OpenID
Flask-RESTful - is an extension for Flask that adds support for quickly building REST
APIs.
Flask-Testing - The Flask-Testing extension provides unit testing utilities for Flask.
I wish you good luck and if you have any feedback please use
http://androidadvance.com contact page.
Chapter 16
This is intended to be sort of "blog posts", regarding flask, python, admin stuff that might
help you on your road ahead.
Logging.
What you want to do:
Log serios errors to a file. Log debug messages on the console. Enable colors on the
console so they appear pretty.
1. create a folder called "utils". inside it create an empty file called __init__.py
this is called a package. and the __init__.py tells python that there are modules to be
imported from this "folder".
2. create colorstreamhandler.py
Google mooware / colorstreamhandler.py and copy-pate it from his gist.
[sidenote: Gist is a simple way to share snippets and pastes with others. All gists are Git
repositories, so they are automatically versioned, forkable and usable from Git. You can
create two kinds of gists: public and private.
3. create general_utils.py
# -*- coding: utf-8 -*import logging
import logging.handlers
import colorstreamhandler
LOG_FILENAME = '../LOCATION/the_log.out'
my_logger = logging.getLogger('MyLogger')
my_logger.setLevel(logging.DEBUG)
file_handler = logging.handlers.RotatingFileHandler(LOG_FILENAME,
maxBytes=10000, backupCount=0)
file_handler.setLevel(logging.ERROR)
my_logger.addHandler(file_handler)
stderr_log_handler = colorstreamhandler.ColorStreamHandler()
stderr_log_handler.setLevel(logging.NOTSET)
my_logger.addHandler(stderr_log_handler)
if category == "error":
my_logger.error(message)
"# -*- coding: utf-8 -*-" is good if you work with russian,chineese characters.
"LOG_FILENAME = '../folder/the_log.out'" si where the_log.out will be
RotatingFileHandler will keep it small...so you don't end up with 200GB log
files.
file_handler.setLevel(logging.ERROR) - we only log ERRORS to the file
now we add another handler and set the logging level to NOTSET so we see
everything in the console.
you should see both messages displayed in color on your console. and just
the error message logged to file.
Homework:
add time to the log in the file (hint: use "%Y-%m-%d %H:%M:%S") and level
name in the console display.
[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[-----[------
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
spoiler
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
alert
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
----------]
file_handler.setFormatter(logging.Formatter("%(asctime)s ### %
(message)s", "%Y-%m-%d %H:%M:%S"))
stderr_log_handler.setFormatter(logging.Formatter("%(levelname)s ### %
(message)s", "%Y-%m-%d %H:%M:%S"))