Writing your first web app using Python and Flask
-
Upload
danielle-madeley -
Category
Technology
-
view
4.611 -
download
3
description
Transcript of Writing your first web app using Python and Flask
Writing your firstweb app usingPython and Flask
Danielle Madeley, Infoxchange blogs.gnome.org/danni dannipenguin
flickr.com/photos/kevinomara/7695277030
If you haven't already...git clone
git://github.com/danni/linux-conf-au-flask-tute
Follow the README to set up yourenvironment
What is Flask and why would you use itRendering your first responseHandling forms and POST dataUsing SQLAlchemyWriting your first RESTful APIHolding open a connection to emit updatesTesting your app with py.testDeploying to Redhat's Openshift platform
import cgi Just kidding!
Just what is Flask?
flickr.com/photos/markstos/3579118891
Rendering yourfirst response
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
if __name__ == '__main__': app.run(debug=True)
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
@app.route('/')def index(): """Homepage"""
return "Hello, linux.conf.au"
if __name__ == '__main__': app.run(debug=True)
x â pythonÿenv�bin�activatex git checkout example÷ÎÏx pip install ÷r requirementsâtxtx python webapp�ÿÿinitÿÿâpy
from flask import redirect, render_template
@app.route('/a-redirect')def a_redirect(): """Redirect the user"""
return redirect(SOME_URL)
@app.route('/a-template')def a_template(): """Render a page using a Jinga2 template"""
return render_template('template.html')
By default: templates arelocated in the module'stemplates/ directory:
webapp/__init__.pytemplates/
template.html
x git checkout example÷ÎÐx pip install ÷r requirementsâtxtx python webapp�ÿÿinitÿÿâpy
handling querystrings
from flask import request
@app.route('/')def index(): """Return the name query argument"""
return request.args['name']
or doing itRESTfully
@app.route('/item/<int:pk>')def get_item(pk): """Return the item referred to by pk"""
# ...
?ou=Linux+Australia&ou=linux.conf.aurequestâargs�ëouë� �� ëLinux Australiaërequestâargsâgetlist�ëouë� �� �ëLinux Australiaëã ëlinuxâconfâauë� requestâargs�ënot presentë� raises KeyErrorrequestâargsâgetlist�ënot presentë� �� ��
handling forms andPOST data
from flask import request
@app.route('/')def index(): """Return the name POST value"""
return request.form['name']
or better still use aforms library
pip install Flask÷WTF
from flask import Flask, render_templatefrom flask.ext.wtf import Form
from wtforms import TextField
class RegoForm(Form): """A simple rego form"""
email = TextField('Email')
@app.route('/register', methods=('GET', 'POST'))def get_register(): """Handle the registration form"""
form = RegoForm()
if form.validate_on_submit(): return "Success"
return render_template('template.html', form=form)
if __name__ == '__main__': app.secret_key = 'THIS IS REALLY SECRET' app.run(debug=True)
from flask.ext.wtf import Form
from wtforms import TextField, validators
class RegoForm(Form): """A simple rego form"""
email = TextField('Email' validators=(validators.DataRequired(), validators.Email()))
<form method="post"> {{ form.hidden_tag() }} {# for CSRF.. important! #} {{ form.email.label }} {{ form.email }} {% if form.email.errors %} <ul> {% for error in form.email.errors %} <li>{{ error }}</li> {% endfor %} </ul> {% endif %} <input type="submit"></form>
SQL alchemy pip install Flask÷SQLAlchemy
from flask.ext.sqlalchemy import SQLAlchemy
db = SQLAlchemy(app)
class User(db.Model): """A user in my database"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True)
# ...
if __name__ == "__main__": app.config['SQLALCHEMY_DATABASE_URI'] = \ 'postgresql://username:password@localhost/myapp' app.run(debug=True)
user = db.session.query(User)\ .filter(User.user_id == user_id)\ .one()
+ migrations(with Alembic)
pip install Flask÷Migrate
Detour:
Flask-Script
from flask import Flaskfrom flask.ext.script import Manager
app = Flask(__name__)manager = Manager(app)
if __name__ == '__main__': manager.run()
x python ÿÿinitÿÿâpyusageä ÿÿinitÿÿâpy �÷h� �shellãrunserver� âââ
positional argumentsä �shellãrunserver� shell Runs a Python shell inside Flask application contextâ runserver Runs the Flask development server iâeâ appârun��
optional argumentsä ÷hã ÷÷help show this help message and exit
x python ÿÿinitÿÿâpy runserver � Running on httpä��ÏÐÕâÎâÎâÏäÓÎÎÎ� � Restarting with reloader
from flask import Flaskfrom flask.ext.migrate import Migrate, MigrateCommandfrom flask.ext.script import Managerfrom flask.ext.sqlalchemy import SQLAlchemy
app = Flask(__name__)
manager = Manager(app)
db = SQLAlchemy(app)
migrate = Migrate(app, db)manager.add_command('db', MigrateCommand)
x python webapp�ÿÿinitÿÿâpy db initx git add migrations
x python webapp�ÿÿinitÿÿâpy db migratex git add migrations�versionsx python webapp�ÿÿinitÿÿâpy db upgrade
Tie it all together so far:
Models and Forms
from flask.ext.wtf import Form
from wtforms.ext.sqlalchemy.orm import model_form
class User(db.Model): """A user"""
__tablename__ = 'users'
id = db.Column(db.Integer, primary_key=True) email = db.Column(db.String(256), unique=True)
UserForm = model_form(User, base_class=Form)
from flask.ext.wtf import Form
from wtforms import validatorsfrom wtforms.ext.sqlalchemy.orm import model_form
UserForm = model_form(User, base_class=Form, field_args={ 'email': { 'validators': [validators.Email()], },})
from flask import Flask, request, render_template, redirect, url_for
@app.route('/register', methods=('GET', 'POST'))def register(): """Register a new user"""
obj = User() form = UserForm(request.form, obj)
if form.validate_on_submit(): form.populate_obj(obj) db.session.add(obj) db.session.commit()
return redirect(url_for('register'))
return render_template('template.html', form=form, users=User.query.all())
Emitting events(and other streams)
Detour:
Generators
def fibonacci(): """Generate an infinite Fibonacci sequence""" a = 0 b = 1
while True: yield b c = a + b a = b b = c
for i in fibonacci(): print i
# WARNING: will never end!
def fibonacci(n=10): """Generate an n elements of the Fibonacci sequence""" a = 0 b = 1
for i in xrange(n): yield b c = a + b a = b b = c
for i in fibonacci(): print iÏÏÐÑÓÖÏÑÐÏÑÒÓÓ
We can use agenerator to emitthe response!
from flask import Response, stream_with_context
@app.route('/events/stream')def get_events(): """Return a stream of events"""
@stream_with_context def generate(): """ A generator that returns a single JSON-encoded event, followed by an empty line. """
while True: yield get_event()
return Response(generate(), mimetype='text/event-stream')
x git checkout example÷ÎÓx pip install ÷r requirementsâtxtx python webapp�ÿÿinitÿÿâpy runserver ÷÷threaded
But wait!
Flask will gracefully finish requests.One request will never finish.
from flask.ext.script import (Manager, Server as ServerCommand)
class Server(ServerCommand): def handle(self, *args, **kwargs): app.running = True
super(Server, self).handle(*args, **kwargs)
print "Shutting down" app.running = False
manager.add_command('runserver', Server)
from Queue import Queue
queue = Queue()
@stream_with_contextdef generate(): """ Yield JSON-encoded events """
while app.running: try: item = queue.get(timeout=1)
yield format_messages([item]) queue.task_done() # eat the queue item except Empty: pass
the other sidewarning: javascript ahead
<title>Event Stream</title> <ul id="messages"> {% for message in get_flashed_messages() %} <li>{{ message }}</li> {% endfor %} </ul>
<script src="/static/bower/jquery.js" type="text/javascript"></script><script src="/static/bower/jquery.eventsource.js" type="text/javascript"></script
<script type="text/javascript">
</script>
$(document).ready(function() { $.eventsource({ label: 'connect', dataType: 'json', url: '/events/stream', message: function(data) { $.each(data, function() { $('<li>', { text: this }).appendTo('#messages'); }); } })});
x git checkout example÷ÎÔx pip install ÷r requirementsâtxtx bower install � NâBâ httpä��bowerâio�x python webapp�ÿÿinitÿÿâpy collectstatic
If you're into websockets you can look atgithub.com/kennethreitz/flask-sockets
Testing withpy.test
tests/conftest.pyimport pytest
from webapp import (app as flask_app, db as flask_db)
@pytest.fixture(scope='session')def db(): """Set up the database"""
flask_app.config['TESTING'] = True flask_app.config['SQLALCHEMY_DATABASE_URI'] = ...
flask_db.drop_all() flask_db.create_all()
return flask_db
@pytest.fixture(scope='session')def app(db): """Set up the Flask test client"""
return flask_app.test_client()
tests/test_views.py"""app and db are available in the test scope"""
def test_index(app): """Test I can get the index page"""
rv = app.get('/')
print rv.data
assert '<title>' in rv.data
x git checkout example÷ÎÕx pip install ÷r requirementsâtxtx pyâtest tests�
deploying toOpenShift
To deploy OpenShift runs your top-levelsetup.py
To serve requests OpenShift runswsgi.application.application
www.openshift.com/developers/python
setup.pyimport os
from setuptools import setup, find_packages
PROJECT_ROOT = os.environ.get('OPENSHIFT_REPO_DIR', os.path.dirname(os.path.abspath(__file__)))
with open(os.path.join(PROJECT_ROOT, 'requirements.txt')) as file_: requirements = [req.strip() for req in file_.xreadlines()]
setup(name='example', version='0.0', author='Danielle Madeley', author_email='[email protected]', url='https://github.com/danni/linux-conf-au-flask-tute', description='Example deploy to OpenShift', install_requires=requirements, )
wsgi/application.pyimport osimport sys
# add local codesys.path.append(os.path.join(os.environ['OPENSHIFT_REPO_DIR']))
# initialise virtual environmentvirtenv = os.environ['OPENSHIFT_HOMEDIR'] + 'python-2.7/virtenv/'os.environ['PYTHON_EGG_CACHE'] = os.path.join(virtenv, 'lib/python2.7/site-packages'
virtualenv = os.path.join(virtenv, 'bin/activate_this.py')
try: execfile(virtualenv, dict(__file__=virtualenv))except IOError: pass
# import and configure WSGI applicationfrom webapp import app as application
app = Flask(__name__)
app.secret_key = os.environ.get('OPENSHIFT_SECRET_TOKEN', 'THIS IS REALLY SECRET')app.config['SQLALCHEMY_DATABASE_URI'] = \ os.environ.get('OPENSHIFT_POSTGRESQL_DB_URL', 'sqlite:///../app.db')
try: app.static_folder = os.path.join(os.environ['OPENSHIFT_REPO_DIR'], 'wsgi'except KeyError: pass
(assuming you've set up Openshift)x rhc app create example python÷ÐâÕ ÷÷no÷gitx git remote add rhc sshä��âââ�example÷âââ�£�git�exampleâgit�
x rhc cartridge add ÷c postgresql÷×âÐ ÷a example
x git push rhc ÷÷force example÷ÎÖämaster
setup.pymanage.pywebapp
__init__.pymanager.pymodels.pyviews.pycollectstatic.pytemplates
events.htmltemplate.html
Going FurtherGetting Bigger
class based viewsfrom flask.views import MethodView
class UserAPI(MethodView):
def get(self): users = User.query.all() ...
def post(self): user = User.from_form_data(request.form) ...
app.add_url_rule('/users/', view_func=UserAPI.as_view('users'))
blueprintsfrom flask import Blueprint, render_template
simple_page = Blueprint('simple_page', __name__, template_folder='templates')
@simple_page.route('/<page>')def show(page): return render_template('pages/%s.html' % page) </page>
blueprintsfrom flask import Flaskfrom module.simple_page import simple_page
app = Flask(__name__)app.register_blueprint(simple_page)
fin ;-)github.com/danni/linux-conf-au-flask-tute
blogs.gnome.org/danni dannipenguin
flickr.com/photos/mau3ry/3763640652