Ned Jackson Lovely
njl@njl.us
@nedjl
Python Code Referenced In This Talk
Django
Whatever wasn't the Lawrence Journal‑World was Django
WSGI
Web Server Gateway Interface
PEP-333/PEP-3333
Werkzeug is a collection of tools for WSGI
Jinja2
Fast, Django-inspired Python Templates
Jinja2 + Werkzeug April Fools Joke
Flask
More "toolkit" than "framework"
Werkzeug + Jinja2 + Some Glue = Flask
Trivial Hello World
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def splash():
return '<h1>Hello World!</h1>'
if __name__ == '__main__':
app.run(debug=True, port=4001)
Flask Object
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
"""
.
.
.
"""
if __name__ == '__main__':
app.run(debug=True, port=4001)
Production
gunicorn -w 4 -b 127.0.0.1:4000 hello_world_one:app
Binding Functions to URLs
@app.route('/')
def splash():
return 'Hello World!
'
Variables in the Path
@app.route('/post/<int:post_id>')
def show_post(post_id):
pass
@app.route('/user/<username>')
def show_user_profile(username):
pass
Reverse with url_for()
url_for('show_post', post_id=27)
url_for('show_user_profile', username="Bob")
url_for('splash')
Can Filter By Action
@app.route('/login')
def login_on_default_get():
pass
@app.route('/login', methods=['POST'])
def login_on_post():
pass
Can Return Different Things
str
/ unicode
(response, status, headers)
flask.Response
/ current_app.response_class
- WSGI Function
JSON API Responses
#from flask import jsonify
from json import dumps
from flask import current_app
def jsonify(**kwargs):
C = current_app.response_class
return C(dumps(kwargs), mimetype='application/json')
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
@app.route('/')
def splash():
return '<h1>Hello World!</h1>'
if __name__ == '__main__':
app.run(debug=True, port=4001)
Tests Let Me Sleep
from hello_world_one import app
def test_splash_page():
client = app.test_client()
response = client.get('/')
assert response.status_code == 200
assert 'Hello' in response.data
#!/usr/bin/env python
from flask import Flask
app = Flask(__name__)
def bad_function():
x = 27
raise Exception("Error over here")
@app.route('/whoops')
def whoops():
bad_function()
return 'Hooray!'
@app.route('/')
def splash():
return '<a href="/whoops">here</a>'
if __name__ == '__main__':
app.run(debug=True, port=4005)
The Template
<!DOCTYPE html>
<html>
<head><title>Hello!</title></head>
<body>
{% if user %}
<h1>Hello, {{user|replace("e", "3")}}!</h1>
{% else %}
<form>
<input name="user" placeholder="Your Name">
</form>
{% endif %}
</body>
</html>
> ls -l
-rwxr-xr-x 1 n staff 197 Mar 23 19:40 hello_world_two.py
drwxr-xr-x 2 n staff 68 Mar 24 18:51 static
drwxr-xr-x 5 n staff 170 Mar 23 19:55 templates
Template Hello World
#!/usr/bin/env python
from flask import Flask, render_template, request
app = Flask(__name__)
@app.route('/')
def splash():
user = request.values.get('user')
return render_template('hello.html', user=user)
if __name__ == '__main__':
app.run(debug=True, port=4002)
Some Tests
from hello_world_two import app
def test_splash_page():
client = app.test_client()
response = client.get('/')
assert response.status_code == 200
assert 'form' in response.data
def test_name_submission():
client = app.test_client()
for name in [u'Ned', u"\u2603"]:
response = client.get(u'/?user={}'.format(name))
assert response.status_code == 200
data = response.data.decode('utf8')
assert u'Hello,' in data
assert name.replace('e', '3') in data
assert 'form' not in response.data
Thread-Local Proxies
from flask import request, session
from flask import g, current_app
request.form #Form values
request.args #Query string
request.values #Combined
request.data #Raw form data
request.files
request.headers
request.path #(url, script_root, base_url, url_root)
request.get_json()
"...."
app.secret_key = 'SOMETHING LONG AND RANDOM'
session['key'] = value
#tricky:
session['thing']['other_thing'] = 'new value'
session.modified = True
session.new
session.modified
session.permanent
@app.before_request
def decorate_request():
uid = session.get('user_id')
if uid:
g.user = some_function(uid)
else:
g.user = None
"Flash" Hello World
#!/usr/bin/env python
from flask import (Flask, render_template, request,
flash, url_for, redirect)
app = Flask(__name__)
app.secret_key = "Something unique and secret"
@app.route('/')
def splash():
return render_template('flash.html')
@app.route('/', methods=['POST'])
def add_name():
user = request.form.get('user', '')
flash('Hello, {}!'.format(user))
return redirect(url_for('splash'))
if __name__ == '__main__':
app.run(debug=True, port=4003)
<!DOCTYPE html>
<html>
<head><title>Hello!</title></head>
<body>
{% with messages = get_flashed_messages() %}
{% if messages %}
{% for msg in messages %}
<h1>{{msg}}</h1>
{% endfor %}
{% endif %}
{% endwith %}
<form method="POST">
<input name="user" placeholder="Your Name">
</form>
</body>
</html>
Some Logic
import redis, json
r = redis.Redis()
MAX_LENGTH = 20
KEY = 'MESSAGES'
def add_message(user, msg):
r.lpush(KEY, json.dumps(dict(user=user, msg=msg)))
r.ltrim(KEY, 0, MAX_LENGTH-1)
def get_messages():
rv = [json.loads(x) for x in r.lrange(KEY, 0, -1)]
return rv
app = Flask(__name__)
app.secret_key = 'SOMETHING LONG AND RANDOM'
@app.route('/')
def splash():
msgs = logic.get_messages()
return render_template('home.html', msgs=msgs)
@app.route('/login', methods=['POST'])
def login():
session['user'] = request.values.get('user', 'A. Non')
return redirect(url_for('splash'))
@app.route('/add_post', methods=['POST'])
def add_post():
msg = request.values.get('msg', "")
logic.add_message(session['user'], msg)
return redirect(url_for('splash'))
<!DOCTYPE html>
<html><head></head>
<body>
<ul>
{% for msg in msgs %}
<li><strong>{{msg.user}}</strong>: {{msg.msg}}</li>
{% endfor %}
</ul>
{% if session.user %}
<form method="POST" action="{{url_for('add_post')}}">
<input name="msg" placeholder="Your Message">
</form>
{% else %}
<form method="POST" action="{{url_for('login')}}">
<input name="user" placeholder="Your Name">
</form>
{% endif %}
</body>
</html>
import board
def test_login():
with board.app.test_client() as c:
r = c.get('/')
assert "Your Name" in r.data
assert "/login" in r.data
r = c.post('/login', data=dict(user='Bob'),
follow_redirects=True)
assert "Your Message" in r.data
assert "/login" not in r.data
assert "/add_post" in r.data
r = c.post('/add_post',
data=dict(msg='Hello World!'),
follow_redirects=True)
assert 'Bob' in r.data
assert 'Hello World!' in r.data
assert '/add_post' in r.data
WTForms
class PastebinEdit(Form):
language = SelectField(u'Programming Language',
choices=PASTEBIN_LANGUAGES)
code = TextAreaField()
SQLAlchemy
class User(Base):
__tablename__ = 'users'
id = Column(Integer, primary_key=True)
name = Column(String)
fullname = Column(String)
password = Column(String)
def __repr__(self):
return "<User(name='%s', fullname='%s', password='%s')>" % (
self.name, self.fullname, self.password)
Configuration
app = Flask(__name__)
app.config.from_object('yourapplication.default_settings')
app.config.from_envvar('YOURAPPLICATION_SETTINGS')
Configuration
$ export YOURAPPLICATION_SETTINGS=/path/to/settings.cfg
$ ./run-app.py
* Running on http://127.0.0.1:5000/
* Restarting with reloader...
from flask import Blueprint, redirect, url_for
from redis import Redis
bp = Blueprint('counter', __name__)
r = Redis()
KEY = 'counter'
@bp.route('/')
def show_counter():
rv = '''<h1>{}</h1><a href="up">Up</a>
<br><a href="down">Down</a>'''
return rv.format(r.get(KEY))
@bp.route('/up')
def counter_up():
r.incr(KEY)
return redirect(url_for('.show_counter'))
@bp.route('/down')
def counter_down():
r.incr(KEY, -1)
return redirect(url_for('.show_counter'))
#!/usr/bin/env python
from flask import Flask, url_for
from counter import bp
app = Flask(__name__)
app.register_blueprint(bp, url_prefix='/counter')
@app.route('/')
def splash():
url = url_for('counter.show_counter')
return 'Good stuff <a href="{}">here</a>'.format(url)
if __name__ == '__main__':
app.run(debug=True, port=4005)
API
import heli
from flask import Flask, request, jsonify
app = Flask(__name__)
h = heli.Heli()
_ARGS = ('yaw', 'pitch', 'throttle', 'trim',)
@app.route('/api', methods=['POST'])
def api():
for k,v in request.values.items():
if k not in _ARGS:
continue
setattr(h, k, int(v))
h.send()
return jsonify(**{k:getattr(h,k) for k in _ARGS})
Thank You!
Ned Jackson Lovely
njl@njl.us
@nedjl