Writing your first web app using Python and Flask

Post on 29-Nov-2014

4.613 views 3 download

description

This tutorial will help you create your first webapp using Python and the Flask microframework. We will cover the following topics: * What is Flask and why would you use it * Rendering your first response * Handling forms and POST data * Using SQLAlchemy * Writing your first RESTful API * Holding open a connection to emit updates * Testing your app with py.test * Deploying to Redhat's Openshift platform Familiarity with Python is assumed. A passing familiarity with Javascript is recommended by not essential. https://github.com/danni/linux-conf-au-flask-tute http://mirror.linux.org.au/linux.conf.au/2014/Thursday/87-Writing_your_first_web_app_using_Python_and_Flask_-_Danielle_Madeley.mp4

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='danielle@madeley.id.au', 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