Web development with Python Flask
Python Flask intro
- Flask
- Jinja
- Werkzeug
Flask is a light-weight web micro-framework in Python. By default it uses a templating system called Jinja and a WSGI web application library called Werkzeug.
In Flask you map URL paths to functions so when someone reaches the endpoint defined by the path the specific function is executed. The function is expected to return the HTML to be displayed.
It is very easy to get started with Flask both if you'd like to build a web-site with HTML pages or if you are interested in providing an API that returns JSON.
Python Flask installation
There is nothing special. You install flask as you'd install any other Python package. Using some kind of virtual envrionment is recommended here too.
virtualenv venv -p python3
source venv/bin/activate
pip install flask
Flask: Hello World
- Flask
- route
- app
The de-facto standard first thing to do in programming in every language or framework is to print "Hello World" on the screen. This is what we are going to do with Flask now. This is probably the most simple Flask application.
For this we need to create regular Python file, for example app.py. I'd recommend you first create a new empty directory and put the file inside that directory.
The first thing is to load the Flask class from the flask module.
Then we create an object representing the whole application. You could use any variable name for this, but the "standard" is to use the name app.
Then comes the interesting part. We declare a function that will return the HTML page.
In our example the name of the function is main, but the name actually is not important. You could as well call it some_other_name.
The important part is the decorator above it. That decorator means that if someone reaches the path /
on the web site, this function will
be executed and whatever it returns will be sent back to the browser. This mapping of a path to a function is called URL routing and we'll
discuss it in detail later on.
For now, let's see how we can use this.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def main():
return "Hello World!"
Flask: Run Hello World
- FLASK_DEBUG
- FLASK_APP
- run
In order to run this we need to set the environment variable FLASK_APP to the name of the file without the extension. We can also set the FLASK_DEBUG environment variable to 1 tuning on the debug-mode, but this is not required for this example.
Then we execute flask run
.
It is a bit different how you do this on Linux or Mac vs. on MS Windows.
In either case once flask is running you can use your browser to visit your new web application on http://127.0.0.1:5000/
You can also use curl
on the command line to see the content of the web page.
Once you have enough, you can hit Ctr-C to stop the program.
Linux/Mac:
$ export FLASK_APP=app
$ export FLASK_DEBUG=1
$ flask run
or
FLASK_APP=app FLASK_DEBUG=1 flask run
Visit: http://127.0.0.1:5000/
curl http://localhost:5000/
Windows on the command line or in the terminal of Pycharm:
set FLASK_APP=app
set FLASK_DEBUG=1
flask run
Other parameters
$ FLASK_APP=echo.py FLASK_DEBUG=1 flask run --port 8080 --host 0.0.0.0
- To stop it use
Ctrl-C
Flask: testing hello world
Before we go ahead learning how to create more complex web applications we need to learn another very important feature of Flask.
Flask makes it very easy to test your web application without even running a web server.
For this we created a file called test_app.py
in the same folder as we have the app.py
with the following content.
The name of the files must start with the word test_
, but otherwise you can pick any filename.
Inside we import the application and we have a test function, again its name must start with test_
. From the app
we can get the test_client
which is a representation of our running web application.
Then we can send in various requests. In this case we sent in an HTTP GET request to the root of the site.
We get back a response object that we can then interrogate with various assertions.
To run this we'll need to install pytest
and then we can just type in pytest
. It will find and run the tests.
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert rv.data == b'Hello World!'
pytest
Flask generated page - time
Showing a static text in the previous example was already a success, but we would like to be able to have a more dynamic web site. In this example we'll see how to display the current time.
We have the same skeleton as in the previous example, but this time the main
function serving the root path returns some HTML
that will be displayed as a link to the /time
path.
We also have a second route mapping the /time
path to the show_time
function.
We run the application the same way as before on the command line.
Now if we access the http://127.0.0.1:5000/
URL we'll see the text time
that we can click on. When we click on it we arrive at the
http://127.0.0.1:5000/time
page that shows the current time. Actually it will show the number of seconds from the epoch,
which is January 1, 1970, 00:00:00 (UTC).
Something like this: 1594528012.7892551
from flask import Flask
import time
app = Flask(__name__)
@app.route("/")
def main():
return '<a href="/time">time</a>'
@app.route("/time")
def show_time():
return str(time.time())
FLASK_APP=app FLASK_DEBUG=1 flask run
Flask generated page - time tested
How can we test the version of our application that also has a generated page? We need to test two pages.
There are many ways to divide our tests.
- We could put all the tests in a single test-function.
- We could have two test-functions in the same test-file.
- We could have two test-functions in two separate test-files.
Putting them in separate functions allows us to run them separately. It also means that if one fails the other might still pass. Usually we put independent test-cases in separate functions. Because it is still so small, putting them in two separate files seems to be an overkill.
The test_home
function is relatively straight forward. We get the main page and then we check the status-code and the exact match for the content.
The test_time
function is trickier. We can't check an exact match as the timestamp will be different every time we run the code. We could mock
the time, but we are looking for a simpler solution. So instead of an exact match we use a regexp to check if the result looks like a number we would
expect.
You can run the tests by running pytest
.
import app
import re
def test_home():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/time">time</a>'
def test_time():
web = app.app.test_client()
rv = web.get('/time')
assert rv.status == '200 OK'
assert re.search(r'\d+\.\d+$', rv.data.decode('utf-8'))
pytest
Flask: Echo GET
- request
- request.args
request.args is a dictionary. We could write request.args['name'] but then it would raise and excpetion and the whole application would crash if the user did not send in a value for the "name" field. We could check if the key exists before trying to access the value using the "in" operator, but that seems like a bit of a waste of work here. Instead we call the "get" method that every dictionary in Python has. It will return None, if the key "name" did not exists. We could even provide a default value to the "get" method".
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def main():
return '''
<form action="/echo" method="GET">
<input name="text">
<input type="submit" value="Echo">
</form>
'''
@app.route("/echo")
def echo():
user_text = request.args.get('text', '')
if user_text:
return "You said: " + user_text
return "Nothing to say?"
Flask: Echo GET - testing
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert '<form action="/echo" method="GET">' in rv.data.decode('utf-8')
rv = web.get('/echo')
assert rv.status == '200 OK'
assert b"Nothing to say?" == rv.data
rv = web.get('/echo?text=foo+bar')
assert rv.status == '200 OK'
assert b"You said: foo bar" == rv.data
Flask: Echo GET - client
- requests
- curl
curl http://localhost:5000/
curl http://localhost:5000/echo?text=Sanch+Panza
import requests
res = requests.get('http://localhost:5000/')
print(res.status_code)
print(res.text)
res = requests.get('http://localhost:5000/echo?text=Hello World!')
print(res.status_code)
print(res.text)
Flask: Echo POST
- request
- request.form
There is also a dictionary called "request.form" that is get filled by data submitted using a POST request. This too is a plain Python dictionary. In this case too we can use either the "request.form.get('field', '')" call we can use the "in" operator to check if the key is in the dictionary and then the regular dicstionary look-up.
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def main():
return '''
<form action="/echo" method="POST">
<input name="text">
<input type="submit" value="Echo">
</form>
'''
@app.route("/echo", methods=['POST'])
def echo():
user_text = request.form.get('text', '')
if user_text:
return "You said: " + user_text
return "Nothing to say?"
Flask: Echo POST - testing
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert '<form action="/echo" method="POST">' in rv.data.decode('utf-8')
rv = web.get('/echo')
assert rv.status == '405 METHOD NOT ALLOWED'
assert '<title>405 Method Not Allowed</title>' in rv.data.decode('utf-8')
rv = web.post('/echo')
assert rv.status == '200 OK'
assert b"Nothing to say?" == rv.data
rv = web.post('/echo', data={ "text": "foo bar" })
assert rv.status == '200 OK'
assert b"You said: foo bar" == rv.data
Flask: Echo POST - client
- request
- request.form
- curl
curl --data "text=Sancho Panza" http://localhost:5000/echo
import requests
res = requests.get('http://localhost:5000/')
print(res.status_code)
print(res.text)
res = requests.post('http://localhost:5000/echo', data={"text": "Hello World!"})
print(res.status_code)
print(res.text)
Exercise: Flask calculator
Write a web application that has two entry boxes and a button and that will add the two numbers inserted into the entry boxes. Write two solutions. In one of them the form will be submitted using GET in the other one it will be submitted using POST.
Solution: Flask calculator
- See in the next few slides
Flask GET and POST in two functions
- request
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
return '<a href="/calc">calc</a>'
@app.route("/calc", methods=['GET'] )
def calc_get():
return '''<form method="POST" action="/calc">
<input name="a">
<input name="b">
<input type="submit" value="Compute">
</form>'''
@app.route("/calc", methods=['POST'] )
def calc_post():
a = request.form.get('a', '0')
b = request.form.get('b', '0')
return str(float(a) + float(b))
Flask GET and POST in two functions - testing
import app
import pytest
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<a href="/calc">calc</a>' == rv.data
def test_calc():
web = app.app.test_client()
rv = web.get('/calc')
assert rv.status == '200 OK'
assert b'<form' in rv.data
rv = web.post('/calc', data={'a': 7, 'b': 11})
assert rv.status == '200 OK'
assert b'18.0' == rv.data
Flask GET and POST in one function
- request.method
- methods
from flask import Flask, request
app = Flask(__name__)
@app.route("/")
def index():
return '<a href="/calc">calc</a>'
@app.route("/calc", methods=['GET', 'POST'] )
def calc():
if request.method == 'POST':
a = request.form.get('a', '0')
b = request.form.get('b', '0')
return str(float(a) + float(b))
else:
return '''<form method="POST" action="/calc">
<input name="a">
<input name="b">
<input type="submit" value="Compute">
</form>'''
Flask GET and POST in one function - testing
import app
import pytest
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<a href="/calc">calc</a>' == rv.data
def test_calc():
web = app.app.test_client()
rv = web.get('/calc')
assert rv.status == '200 OK'
assert b'<form' in rv.data
rv = web.post('/calc', data={'a': 7, 'b': 11})
assert rv.status == '200 OK'
assert b'18.0' == rv.data
Flask Logging
Logging is a very usefule tool to avoid the need for manual debugging. It also can provide insight as to what happened in a session on the production server. During development you'll be able to see the messages on the terminal where Flask runs. On the production server it can be saved in a log file for later review.
There are several pre-defined levels of logging. You can use the specific functions to indicate the importance of each log message.
You can set the level of logging inside the code or in an external configuration file.
from flask import Flask
import logging
app = Flask(__name__)
# minimum log level defaults to warning
# we can set the minimum loge level
app.logger.setLevel(logging.INFO)
app.logger.debug('debug')
app.logger.info('info')
app.logger.warning('warning')
@app.route("/")
def main():
app.logger.debug("Some debug message")
app.logger.info("Some info message")
app.logger.warning("Some warning message")
app.logger.error("Some error message")
return "Hello World"
Flask URL routing
The mapping of the path part of a URL, so the one that comes after the domain name and after the port number (if it is included) is the path. Mapping that to a function call is called routing.
In the following pages we are going to see several examples on how to map routes to functions.
It is also called "url route registration".
Flask Path or route parameters
In addition to having routes for fixed pathes, Flask can also handle routes where one or more parts of the path can have any value.
It can be especially useful if the response is then looked up in some sort of a database.
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return '''
Main<br>
<a href="/user/23">23</a><br>
<a href="/user/42">42</a><br>
<a href="/user/Joe">Joe</a><br>
'''
@app.route("/user/<uid>")
def api_info(uid):
return uid
FLASK_APP=app.py FLASK_DEBUG=0 flask run
Flask Path or route parameters - testing
import app
import pytest
@pytest.fixture()
def web():
return app.app.test_client()
def test_app(web):
rv = web.get('/')
assert rv.status == '200 OK'
assert b'Main<br>' in rv.data
@pytest.mark.parametrize('uid', ['23', '42', 'Joe'])
def test_user(web, uid):
rv = web.get(f'/user/{uid}')
assert rv.status == '200 OK'
assert uid == rv.data.decode('utf-8')
def test_user_fail(web):
rv = web.get(f'/user/')
assert rv.status == '404 NOT FOUND'
def test_user_fail(web):
rv = web.get(f'/user')
assert rv.status == '404 NOT FOUND'
- The route /user/ returns 404 not found
The test here get a bit complex. We have 3 different tests function. Each one needs the variable returned by the test_client
function.
While the route with the parameter can handle any value, there is one route that it does not handle. If the visitor reached the page /user/ page either by mistake or by removing the parameter in the URL, then we'll see a "404 Not Found" page.
Flask route parameters - separate route to root page
One way to overcome the starnge situation that /user/Foo works but /user/ gives a 404 Not Found error is to add an extra route that will specifically handle that case.
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def index():
return '''
Main<br>
<a href="/user/23">23</a><br>
<a href="/user/42">42</a><br>
<a href="/user/Joe">Joe</a><br>
'''
@app.route("/user/<uid>")
def api_info(uid):
return uid
@app.route("/user/")
def user():
return 'User List'
FLASK_APP=app.py FLASK_DEBUG=0 flask run
With the tests we can see that now /user/ works and returns the value we expected. Howerver there is anothe special address and that is the /user path, the one without the trailing slash. Flask will handle this too by automatiocally redirecting the browser to the /user/ page.
You can see uisng the test case that the /user path returns a '308 Permanent Redirect' error and sets the "Location" in the response header. Regular browsers would automatically move to the /user/ page and show the response from there.
Flask will also send some HTML content explaining the situation. This is only relevant for browsers, or other HTTP clients that don't automatically follow the redirection.
For example curl will not automaticall follow this redirection. However if you supply the -L flag then it will.
curl -L http://127.0.0.1:5000/user
import app
import pytest
@pytest.fixture()
def web():
return app.app.test_client()
def test_app(web):
rv = web.get('/')
assert rv.status == '200 OK'
assert b'Main<br>' in rv.data
@pytest.mark.parametrize('uid', ['23', '42', 'Joe'])
def test_user(web, uid):
rv = web.get(f'/user/{uid}')
assert rv.status == '200 OK'
assert uid == rv.data.decode('utf-8')
def test_user_root_slash(web):
rv = web.get(f'/user/')
assert rv.status == '200 OK'
assert b'User List' == rv.data
def test_user_root(web):
rv = web.get(f'/user')
assert rv.status == '308 PERMANENT REDIRECT'
assert rv.headers['Location'] == 'http://localhost/user/'
assert b'<p>You should be redirected automatically to target URL:' in rv.data
Flask route parameters - default values
from flask import Flask
app = Flask(__name__)
@app.route("/")
def index():
return '''
Main<br>
<a href="/user/23">23</a><br>
<a href="/user/42">42</a><br>
<a href="/user/Joe">Joe</a><br>
'''
@app.route("/user/", defaults={ 'uid': 'zero' })
@app.route("/user/<uid>")
def api_info(uid):
return uid
import app
import pytest
@pytest.fixture()
def web():
return app.app.test_client()
def test_app(web):
rv = web.get('/')
assert rv.status == '200 OK'
assert b'Main<br>' in rv.data
@pytest.mark.parametrize('uid', ['23', '42', 'Joe'])
def test_user(web, uid):
rv = web.get(f'/user/{uid}')
assert rv.status == '200 OK'
assert uid == rv.data.decode('utf-8')
def test_user_root_slash(web):
rv = web.get(f'/user/')
assert rv.status == '200 OK'
assert b'zero' == rv.data
def test_user_root(web):
rv = web.get(f'/user')
assert rv.status == '308 PERMANENT REDIRECT'
assert rv.headers['Location'] == 'http://localhost/user/'
assert b'<p>You should be redirected automatically to target URL:' in rv.data
Flask Path or route parameters (int)
- int
from flask import Flask
app = Flask(__name__)
@app.route("/")
def main():
return '''
Main<br>
<a href="/user/23">23</a><br>
<a href="/user/42">42</a><br>
<a href="/user/Joe">Joe</a><br>
'''
@app.route("/user/<int:uid>")
def api_info(uid):
return str(uid)
FLASK_APP=app.py FLASK_DEBUG=0 flask run
Flask Path or route parameters add (int)
from flask import Flask
app = Flask(__name__)
@app.route('/')
def main():
return 'Main'
@app.route('/<op>/<int:a>/<int:b>')
def calc(op, a, b):
if op == 'add':
return str(a+b)
if op == 'mul':
return str(a*b)
return 'Invalid operator'
@app.route('/sum/<path:values>')
def sum_route(values):
numbers = map(float, values.split('/'))
total = sum(numbers)
return str(total)
FLASK_APP=app.py FLASK_DEBUG=0 flask run
Flask Path or route parameters add (path)
-
path
-
Accept any path, including slashes:
from flask import Flask
app = Flask(__name__)
@app.route("/")
def main():
return '''
Main<br>
<a href="/user/name">/user/name</a><br>
<a href="/user/other/dir">/user/other/dir</a><br>
<a href="/user/hi.html">/usre/hi.html</a><br>
'''
@app.route("/user/<path:fullpath>")
def api_info(fullpath):
return fullpath
FLASK_APP=app.py FLASK_DEBUG=0 flask run
Exercise: Calculator with path
- Write an application that works like a calculator based on the path:
/add/2/3 displays 2+3=5
/mul/3/4 displays 3*4=12
Then also add the following:
/sum/2/1.4/-78
- Write tests for each case
Solution: Calculator with path
from flask import Flask
app = Flask(__name__)
@app.route('/')
def main():
return 'Main'
@app.route('/<op>/<int:a>/<int:b>')
def calc(op, a, b):
if op == 'add':
return str(a+b)
if op == 'mul':
return str(a*b)
return 'Invalid operator'
@app.route('/sum/<path:values>')
def sum_route(values):
numbers = map(float, values.split('/'))
total = sum(numbers)
return str(total)
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'Main' == rv.data
rv = web.get('/add/2/3')
assert rv.status == '200 OK'
assert b'5' == rv.data
rv = web.get('/mul/2/3')
assert rv.status == '200 OK'
assert b'6' == rv.data
rv = web.get('/sum/2/3/4.1/-1')
assert rv.status == '200 OK'
print(rv.data)
assert b'8.1' == rv.data
Flask Redirect
-
redirect
-
redirect(external url)
from flask import Flask, redirect
app = Flask(__name__)
@app.route('/')
def index():
return '<a href="/cm">Go to Code Maven</a>'
@app.route('/cm')
def cm():
return redirect('https://code-maven.com/')
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/cm">Go to Code Maven</a>'
rv = web.get('/cm')
assert rv.status == '302 FOUND'
assert rv.headers['Location'] == 'https://code-maven.com/'
assert b'<p>You should be redirected automatically to target URL: <a href="https://code-maven.com/">https://code-maven.com/</a>' in rv.data
Flask Internal Redirect with url_for
-
redirect
-
url_for
-
url_for('login') Returns the url based on the functions name
from flask import Flask, redirect, url_for
app = Flask(__name__)
@app.route('/')
def index():
return '<a href="/goto">Go to</a>'
@app.route('/goto')
def goto():
return redirect(url_for('user_page'))
@app.route('/user')
def user_page():
return 'User page'
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/goto">Go to</a>'
rv = web.get('/goto')
assert rv.status == '302 FOUND'
assert rv.headers['Location'] == 'http://localhost/user'
assert b'<p>You should be redirected automatically to target URL: <a href="/user">/user</a>' in rv.data
rv = web.get('/user')
assert rv.status == '200 OK'
assert rv.data == b'User page'
Flask Internal Redirect with parameters
- url_for('login', key=value) Pass parameters to the URL path keys that are not part of the route definitions will be converted to query string values
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
@app.route('/')
def index():
return '''<form action="/goto" method="POST">
<input name="username">
<input type="submit" value="Go">
</form>'''
@app.route('/goto', methods=['POST'])
def login_post():
username = request.form.get('username')
if username is None or username == '':
return redirect(url_for('user_page_central'))
return redirect(url_for('user_page', name = username))
@app.route('/user/')
def user_page_central():
return 'List of users'
@app.route('/user/<name>')
def user_page(name):
return f'Page of {name}'
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<form action="/goto" method="POST">' in rv.data
rv = web.post('/goto', data={'username': 'Joe'})
assert rv.status == '302 FOUND'
assert rv.headers['Location'] == 'http://localhost/user/Joe'
assert b'<p>You should be redirected automatically to target URL: <a href="/user/Joe">/user/Joe</a>' in rv.data
rv = web.post('/goto')
assert rv.status == '302 FOUND'
assert rv.headers['Location'] == 'http://localhost/user/'
assert b'<p>You should be redirected automatically to target URL: <a href="/user/">/user/</a>' in rv.data
rv = web.get('/user/Jane')
assert rv.status == '200 OK'
assert rv.data == b'Page of Jane'
rv = web.get('/user/')
assert rv.status == '200 OK'
assert rv.data == b'List of users'
Exercise: Random redirect
- Create an application that has a route called /random that will randomly redirect the visitor to on of several pre-defined URLs.
- Create your own list of pre-defined URLs.
Solution: Random redirect
- [random.choice](https://docs.python.org/library/random.html#random.choice" %}
from flask import Flask, redirect
import random
app = Flask(__name__)
urls = [
'https://code-maven.com/',
'https://perlmaven.com/',
'https://hostlocal.com/',
'https://pydigger.com/',
'https://szabgab.com/',
]
@app.route('/')
def index():
return '<a href="/random">Random</a>'
@app.route('/random')
def random_redirect():
return redirect(random.choice(urls))
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/random">Random</a>'
rv = web.get('/random')
assert rv.status == '302 FOUND'
assert 'https://' in rv.headers['Location']
print(rv.headers['Location'])
assert rv.headers['Location'] in app.urls
Flask Jinja template
-
jinja
-
render_template
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/")
def main():
return render_template('index.html')
@app.route("/echo", methods=['POST'])
def echo():
user_text = request.form.get('text', '')
return "You said: " + user_text
<form action="/echo" method="POST">
<input name="text">
<input type="submit" value="Echo">
</form>
Flask Jinja template - testing
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<form action="/echo" method="POST">' in rv.data
rv = web.post('/echo', data={'text': 'Hello'})
assert rv.status == '200 OK'
assert b'You said: Hello' == rv.data
rv = web.post('/echo')
assert rv.status == '200 OK'
assert b'You said: ' == rv.data
Flask Jinja template with parameters
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/")
def main():
return render_template('echo.html')
@app.route("/echo", methods=['POST'])
def echo():
user_text = request.form.get('text', '')
return render_template('echo.html', text=user_text)
<form action="/echo" method="POST">
<input name="text">
<input type="submit" value="Echo">
</form>
You said: <b>{{ text }}</b>
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<form action="/echo" method="POST">' in rv.data
rv = web.post('/echo', data={ 'text': 'foo bar' })
assert rv.status == '200 OK'
assert b'<form action="/echo" method="POST">' in rv.data
assert b'You said: <b>foo bar</b>' in rv.data
rv = web.post('/echo')
assert rv.status == '200 OK'
assert b'<form action="/echo" method="POST">' in rv.data
assert b'You said: <b></b>' in rv.data
Flask Jinja template with conditional
- if
- endif
from flask import Flask, request, render_template
app = Flask(__name__)
@app.route("/")
def main():
return render_template('echo.html')
@app.route("/echo", methods=['POST'])
def echo():
user_text = request.form.get('text', '')
return render_template('echo.html', text=user_text)
<form action="/echo" method="POST">
<input name="text">
<input type="submit" value="Echo">
</form>
{% if text %}
You said: <b>{{ text }}</b>
{% else %}
You did not say anything.
{% endif %}
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<form action="/echo" method="POST">' in rv.data
rv = web.post('/echo', data={ 'text': 'foo bar' })
assert rv.status == '200 OK'
assert b'<form action="/echo" method="POST">' in rv.data
assert b'You said: <b>foo bar</b>' in rv.data
rv = web.post('/echo')
assert rv.status == '200 OK'
assert b'<form action="/echo" method="POST">' in rv.data
assert b'You said' not in rv.data
assert b'You did not say anything.' in rv.data
Flask Jinja template with loop
- for
- in
- endfo
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def main():
languages = [
'English',
'Spanish',
'Hebrew',
'Hungarian',
]
return render_template('main.html',
title = "Code Maven Jinja example",
languages = languages,
)
<h1>{{ title }}</h1>
<ul>
{% for lang in languages %}
<li>{{ lang }}</li>
{% endfor %}
</ul>
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<h1>Code Maven Jinja example</h1>' in rv.data
assert b'<li>Hungarian</li>' in rv.data
Flask Jinja template dictionary
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def main():
person = {
'fname' : 'Mary',
'lname' : 'Ann',
'email' : 'mary-ann@example.com',
}
return render_template('main.html',
title = 'Person',
person = person,
)
<h1>{{ title }}</h1>
<h2>{{ person.fname}} {{ person.lname }}</h2>
<table>
{% for key in person.keys() %}
<tr><td>{{ key }}</td><td>{{ person[key] }}</td></tr>
{% endfor %}
</table>
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<h1>Person</h1>' in rv.data
assert b'<h2>Mary Ann</h2>' in rv.data
assert b'<td>Mary</td>' in rv.data
Flask Jinja template list of dictionaries
from flask import Flask, render_template
import csv
app = Flask(__name__)
@app.route("/")
def main():
planets = read_csv_file('planets.csv')
return render_template('main.html',
title = "Planets",
planets = planets,
)
def read_csv_file(filename):
planets = []
with open(filename) as fh:
rd = csv.DictReader(fh, delimiter=',')
for row in rd:
planets.append(row)
return planets
<h1>{{ title }}</h1>
<table>
{% for planet in planets %}
<tr>
<td>{{ planet['Planet name'] }}</td>
<td>{{ planet['Distance (AU)'] }}</td>
<td>{{ planet['Mass'] }}</td>
</tr>
{% endfor %}
</table>
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<h1>Planets</h1>' in rv.data
assert b'<td>Mercury</td>' in rv.data
Flask Jinja include
.
├── app.py
├── test_app.py
└── templates
├── incl
│ ├── footer.html
│ └── header.html
└── main.html
from flask import Flask, render_template
app = Flask(__name__)
@app.route('/')
def main():
languages = [
{
'name': 'Python',
'year': 1991,
},
{
'name': 'JavaScript',
'year': 1995,
},
{
'name': 'C',
}
]
return render_template('main.html',
title = 'Code Maven Jinja include example',
languages = languages,
)
{% include 'incl/header.html' %}
<h2>Languages</h2>
<ul>
{% for lang in languages %}
<li>{{ lang.name }}
{% if lang.year %}
{{ lang.year }}
{% else %}
Timeless
{% endif %}
</li>
{% endfor %}
</ul>
{% include 'incl/footer.html' %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=yes">
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
</body>
</html>
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<title>Code Maven Jinja include example</title>' in rv.data
assert b'<h1>Code Maven Jinja include example</h1>' in rv.data
assert b'Timeless' in rv.data
Jinja extend template layout block
- layout
- extends
- block
We use extend to connect between templates. We use block both to mark the area that will be replaced and the content that will replace it.
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template('index.html')
@app.route("/page")
def page():
return render_template('page.html')
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<a href="https://code-maven.com/">Code Maven</a>' in rv.data, 'footer'
assert b'<title>Main page</title>' in rv.data, 'title'
assert b'This is the content' in rv.data, 'content'
{% extends 'layouts/base.html' %}
{% block title -%}
Main page
{%- endblock %}
{% block content %}
This is the content
{% endblock %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, user-scalable=yes">
<title>{% block title %}{% endblock %}</title>
</head>
<body>
<a href="/">Home</a>
<hr>
{% block content %}{% endblock %}
<hr>
<a href="https://code-maven.com/">Code Maven</a>
</body>
</html>
Jinja template inheritance - super
-
super
-
Using the super() call we can inherit the content of the block
from flask import Flask, render_template
app = Flask(__name__)
@app.route("/")
def index():
return render_template('index.html')
@app.route("/page")
def page():
return render_template('page.html')
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<a href="https://code-maven.com/">Code Maven</a>' in rv.data, 'footer'
assert b'<title>Demo: Main page</title>' in rv.data, 'main title'
assert b'This is the content' in rv.data, 'main page content'
assert b'original' not in rv.data, 'no inherited content'
def test_page():
web = app.app.test_client()
rv = web.get('/page')
assert rv.status == '200 OK'
assert b'<a href="https://code-maven.com/">Code Maven</a>' in rv.data, 'footer'
assert b'<title>Demo: Other page</title>' in rv.data, 'page title'
assert b'The original content' in rv.data, 'page content'
assert b'This is the content' in rv.data, 'inherited content'
{% extends 'layouts/base.html' %}
{% block title -%}
Main page
{%- endblock %}
{% block content %}
This is the content
{% endblock %}
{% extends 'layouts/base.html' %}
{% block title -%}
Other page
{%- endblock %}
{% block content %}
{{ super() }}
This is the content
{% endblock %}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport"
content="width=device-width, initial-scale=1, user-scalable=yes">
<title>Demo: {% block title %}{% endblock %}</title>
</head>
<body>
<a href="/">Home</a>
<hr>
{% block content %}
The original content
{% endblock %}
<hr>
<a href="https://code-maven.com/">Code Maven</a>
</body>
</html>
Static files
from flask import Flask, request, render_template, url_for
app = Flask(__name__)
@app.route("/")
def main():
return render_template('main.html')
@app.route("/other")
def other():
return render_template('other.html',
img_path = url_for('static', filename='img/python.png'))
<h1>Main page</h1>
<img src="/static/img/python.png">
<p>
<a href="/other">other</a>
<h2>Other page</h2>
img_path: {{ img_path }}
<p>
<img src="{{ img_path }}">
<p>
<a href="/">main</a>
.
├── app.py
├── static
│ └── img
│ └── python.png
└── templates
├── main.html
└── other.html
Flask: Counter
from flask import Flask
app = Flask(__name__)
counter = 1
@app.route("/")
def main():
global counter
counter += 1
return str(counter)
Access the page from several browsers. There is one single counter that lives as long as the process lives.
Color selector without session
from flask import Flask, request, render_template
import re
app = Flask(__name__)
@app.route("/",methods=['GET', 'POST'] )
def main():
color = "FFFFFF"
new_color = request.form.get('color', '')
if re.search(r'^[0-9A-F]{6}$', new_color):
color = new_color
return render_template('main.html', color = color)
<style>
* {
background-color: #{{ color }};
}
</style>
<form method="POST">
<input name="color" value="{{ color }}">
<input type="submit" value="Set">
</form>
<p>
<a href="/">home</a>
Session management
from flask import Flask, request, render_template, session
import re
app = Flask(__name__)
app.secret_key = 'blabla'
@app.route("/",methods=['GET', 'POST'] )
def main():
color = session.get('color', 'FFFFFF')
app.logger.debug("Color: " + color)
new_color = request.form.get('color', '')
if re.search(r'^[0-9A-F]{6}$', new_color):
app.logger.debug('New color: ' + new_color)
session['color'] = new_color
color = new_color
return render_template('main.html', color = color)
Flask custom 404 page
from flask import Flask
app = Flask(__name__)
@app.route("/")
def main():
return '''
Main
<a href="/not">404 page</a>
'''
from flask import Flask
app = Flask(__name__)
@app.route("/")
def main():
return '''
Main
<a href="/not">404 page</a>
'''
@app.errorhandler(404)
def not_found(e):
return "Our Page not found", 404
curl -I http://localhost:5000/not
HTTP/1.0 404 NOT FOUND
Flask Error page
from flask import Flask
app = Flask(__name__)
@app.route("/")
def main():
return '''
Main
<a href="/bad">bad</a>
'''
@app.route("/bad")
def bad():
raise Exception("This is a bad page")
return 'Bad page'
Will not trigger in debug mode!
$ FLASK_APP=echo.py FLASK_DEBUG=0 flask run
curl -I http://localhost:5000/not
HTTP/1.0 500 INTERNAL SERVER ERROR
from flask import Flask
app = Flask(__name__)
@app.route("/")
def main():
return '''
Main
<a href="/bad">bad</a>
'''
@app.route("/bad")
def bad():
raise Exception("This is a bad page")
return 'Bad page'
@app.errorhandler(500)
def not_found(err):
#raise Exception("Oups")
return "Our Page crashed", 500
Exercise: Flask persistent counter
Create a Flask-based application with a persistent counter that even after restarting the application the counter will keep increasing.
Exercise: Flask persistent multi-counter
Create a Flask-based application with a persistent counter that even after restarting the application the counter will keep increasing. For each user have its own counter as identified by the username they type in.
Solution: Flask persistent counter
from flask import Flask
import os
app = Flask(__name__)
def get_counter_file():
data_dir = os.path.dirname(os.path.abspath(__file__))
data_dir = os.environ.get('COUNTER_DATA_DIR', data_dir)
counter_file = os.path.join(data_dir, 'counter.txt')
return counter_file
@app.route("/")
def main():
counter_file = get_counter_file()
counter = 0
if os.path.exists(counter_file):
with open(counter_file) as fh:
counter = int(fh.read())
counter += 1
with open(counter_file, 'w') as fh:
fh.write(str(counter))
return str(counter)
import app
import os
def test_app(tmpdir):
os.environ['COUNTER_DATA_DIR'] = str(tmpdir) # str expected, not LocalPath
web1 = app.app.test_client()
rv = web1.get('/')
assert rv.status == '200 OK'
assert rv.data == b'1'
rv = web1.get('/')
assert rv.status == '200 OK'
assert rv.data == b'2'
web2 = app.app.test_client()
rv = web2.get('/')
assert rv.status == '200 OK'
assert rv.data == b'3'
Solution: Flask persistent multi-counter
from flask import Flask
import os
import json
app = Flask(__name__)
def get_counter_file():
data_dir = os.path.dirname(os.path.abspath(__file__))
data_dir = os.environ.get('COUNTER_DATA_DIR', data_dir)
counter_file = os.path.join(data_dir, 'counter.json')
return counter_file
def read_counters():
counter_file = get_counter_file()
counter = {}
if os.path.exists(counter_file):
with open(counter_file) as fh:
counter = json.load(fh)
return counter
@app.route("/")
def main():
counter = read_counters()
if not counter:
return 'No counter yet. Maybe start by clicking <a href="/python">here</a>'
html = '<table>\n'
for name in sorted(counter.keys()):
html += f'<tr><td><a href="/{name}">{name}</a></td><td>{counter[name]}</td></tr>\n'
html += '</table>'
return html
@app.route("/<name>")
def count(name):
counter = read_counters()
if name not in counter:
counter[name] = 0
counter[name] += 1
counter_file = get_counter_file()
with open(counter_file, 'w') as fh:
json.dump(counter, fh)
return f'<a href="/">home</a> {name}: {counter[name]}'
import app
import os
def test_app(tmpdir):
os.environ['COUNTER_DATA_DIR'] = str(tmpdir) # str expected, not LocalPath
web1 = app.app.test_client()
rv = web1.get('/')
assert rv.status == '200 OK'
assert rv.data == b'No counter yet. Maybe start by clicking <a href="/python">here</a>'
rv = web1.get('/python')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/">home</a> python: 1'
rv = web1.get('/')
assert rv.status == '200 OK'
assert rv.data == b'''<table>
<tr><td><a href="/python">python</a></td><td>1</td></tr>
</table>'''
rv = web1.get('/python')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/">home</a> python: 2'
rv = web1.get('/flask')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/">home</a> flask: 1'
rv = web1.get('/python')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/">home</a> python: 3'
rv = web1.get('/')
assert rv.status == '200 OK'
assert rv.data == b'''<table>
<tr><td><a href="/flask">flask</a></td><td>1</td></tr>
<tr><td><a href="/python">python</a></td><td>3</td></tr>
</table>'''
web2 = app.app.test_client()
rv = web2.get('/python')
assert rv.status == '200 OK'
assert rv.data == b'<a href="/">home</a> python: 4'
rv = web2.get('/')
assert rv.status == '200 OK'
assert rv.data == b'''<table>
<tr><td><a href="/flask">flask</a></td><td>1</td></tr>
<tr><td><a href="/python">python</a></td><td>4</td></tr>
</table>'''
Flask Exercises
Flask login
from flask import Flask, render_template, url_for, redirect, request, session
app = Flask(__name__)
app.secret_key = 'loginner'
users = {
'admin' : 'secret',
'foo' : 'myfoo',
}
@app.route("/")
def main():
return render_template('main.html')
@app.route('/login')
def login_form():
return render_template('login.html')
@app.route('/login', methods=['POST'])
def login():
username = request.form.get('username')
password = request.form.get('password')
if username and password and username in users and users[username] == password:
session['logged_in'] = True
return redirect(url_for('account'))
return render_template('login.html')
@app.route("/account")
def account():
if not session.get('logged_in'):
return redirect(url_for('login'))
return render_template('account.html')
@app.route('/logout')
def logout():
if not session.get('logged_in'):
return "Not logged in"
else:
session['logged_in'] = False
return render_template('logout.html')
{% include 'header.html' %}
Account information.
<div>
<a href="/">home</a> | <a href="/login">login</a> | <a href="/logout">logout</a> | <a href="/account">account</a>
</div>
{% include 'header.html' %}
Home page
{% include 'header.html' %}
<form method="POST">
<input name="username" placeholder="username">
<input name="password" placeholder="password" type="password">
<input type="submit" value="Login">
</form>
{% include 'header.html' %}
Bye bye
{% include 'header.html' %}
Home
Flask JSON API
- jsonify
from flask import Flask, jsonify
app = Flask(__name__)
@app.route("/")
def main():
return '''
Main <a href="/api/info">info</a>
'''
@app.route("/api/info")
def api_info():
info = {
"ip" : "127.0.0.1",
"hostname" : "everest",
"description" : "Main server",
"load" : [ 3.21, 7, 14 ]
}
return jsonify(info)
$ curl -I http://localhost:5000/api/info
HTTP/1.0 200 OK
Content-Type: application/json
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'Main <a href="/api/info">info</a>' in rv.data
rv = web.get('/api/info')
assert rv.status == '200 OK'
#print(rv.data) # the raw json data
assert rv.headers['Content-Type'] == 'application/json'
resp = rv.json
assert resp == {
"ip" : "127.0.0.1",
"hostname" : "everest",
"description" : "Main server",
"load" : [ 3.21, 7, 14 ]
}
Flask and AJAX with Vanila JavaScript
- jsonify
from flask import Flask, jsonify, render_template, request
import time
app = Flask(__name__)
@app.route("/")
def main():
return render_template('main.html', reload = time.time())
@app.route("/api/info")
def api_info():
info = {
"ip" : "127.0.0.1",
"hostname" : "everest",
"description" : "Main server",
"load" : [ 3.21, 7, 14 ]
}
return jsonify(info)
@app.route("/api/calc")
def add():
a = int(request.args.get('a', 0))
b = int(request.args.get('b', 0))
div = 'na'
if b != 0:
div = a/b
return jsonify({
"a" : a,
"b" : b,
"add" : a+b,
"multiply" : a*b,
"subtract" : a-b,
"divide" : div,
})
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'<button id="calc">Calc</button>' in rv.data
rv = web.get('/api/info')
assert rv.status == '200 OK'
#print(rv.data) # the raw json data
assert rv.headers['Content-Type'] == 'application/json'
resp = rv.json
assert resp == {
"ip" : "127.0.0.1",
"hostname" : "everest",
"description" : "Main server",
"load" : [ 3.21, 7, 14 ]
}
rv = web.get('/api/calc?a=7&b=8')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
resp = rv.json
assert resp == {
"a" : 7,
"b" : 8,
"add" : 15,
"multiply" : 56,
"subtract" : -1,
"divide" : 0.875,
}
rv = web.get('/api/calc', query_string={ 'a' : '10', 'b': '2' })
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
resp = rv.json
assert resp == {
"a" : 10,
"b" : 2,
"add" : 12,
"multiply" : 20,
"subtract" : 8,
"divide" : 5,
}
(function() {
var ajax_get = function(url, callback) {
xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == 4 && xmlhttp.status == 200) {
console.log('responseText:' + xmlhttp.responseText);
try {
var data = JSON.parse(xmlhttp.responseText);
} catch(err) {
console.log(err.message + " in " + xmlhttp.responseText);
return;
}
callback(data);
}
};
xmlhttp.open("GET", url, true);
xmlhttp.send();
};
ajax_get('/api/info', function(data) {
console.log('get info');
document.getElementById('info').innerHTML = JSON.stringify(data, null, ' ');
document.getElementById('description').innerHTML = data['description'];
});
var calc = document.getElementById('calc');
calc.addEventListener('click', function() {
document.getElementById('info').style.display = "none";
document.getElementById('description').style.display = "none";
var url = '/api/calc?a=' + document.getElementById('a').value + '&b=' + document.getElementById('b').value;
//console.log(url);
ajax_get(url, function(data) {
document.getElementById('add').innerHTML = data['a'] + ' + ' + data['b'] + ' = ' + data['add'];
document.getElementById('subtract').innerHTML = data['a'] + ' - ' + data['b'] + ' = ' + data['subtract'];
document.getElementById('multiply').innerHTML = data['a'] + ' * ' + data['b'] + ' = ' + data['multiply'];
document.getElementById('divide').innerHTML = data['a'] + ' / ' + data['b'] + ' = ' + data['divide'];
});
});
})()
<html>
<head>
</head>
<body>
<input type="number" id="a">
<input type="number" id="b">
<button id="calc">Calc</button>
<div id="results">
<div id="add"></div>
<div id="subtract"></div>
<div id="multiply"></div>
<div id="divide"></div>
</div>
<pre id="info"></pre>
<div id="description"></div>
<script src="/static/math.js?r={{reload}}"></script>
</body>
</html>
Flask and AJAX with JQuery
- jsonify
from flask import Flask, jsonify, render_template, request
import time
app = Flask(__name__)
@app.route("/")
def main():
return render_template('main.html', reload = time.time())
@app.route("/api/info")
def api_info():
info = {
"ip" : "127.0.0.1",
"hostname" : "everest",
"description" : "Main server",
"load" : [ 3.21, 7, 14 ]
}
return jsonify(info)
@app.route("/api/calc")
def add():
a = int(request.args.get('a', 0))
b = int(request.args.get('b', 0))
div = 'na'
if b != 0:
div = a/b
return jsonify({
"a" : a,
"b" : b,
"add" : a+b,
"multiply" : a*b,
"subtract" : a-b,
"divide" : div,
})
$(function() {
$.ajax({
url: '/api/info',
success: function(data) {
console.log('get info');
$('#info').html(JSON.stringify(data, null, ' '));
$('#description').html(data['description']);
}
});
$('#calc').click(function() {
$('#info').css('display', "none");
$('#description').css('display', "none");
//console.log(url);
$.ajax({
url : '/api/calc?a=' + document.getElementById('a').value + '&b=' + document.getElementById('b').value,
success: function(data) {
$('#add').html(data['a'] + ' + ' + data['b'] + ' = ' + data['add']);
$('#subtract').html(data['a'] + ' - ' + data['b'] + ' = ' + data['subtract']);
$('#multiply').html(data['a'] + ' * ' + data['b'] + ' = ' + data['multiply']);
$('#divide').html(data['a'] + ' / ' + data['b'] + ' = ' + data['divide']);
}
});
});
})
<html>
<head>
</head>
<body>
<input type="number" id="a">
<input type="number" id="b">
<button id="calc">Calc</button>
<div id="results">
<div id="add"></div>
<div id="subtract"></div>
<div id="multiply"></div>
<div id="divide"></div>
</div>
<pre id="info"></pre>
<div id="description"></div>
<script src="/static/jquery-3.1.1.min.js"></script>
<script src="/static/math.js?r={{reload}}"></script>
</body>
</html>
Flask POST JSON data to web application
from flask import Flask, jsonify, render_template, request
import time
app = Flask(__name__)
@app.route("/")
def main():
return 'Post JSON to /api/calc'
@app.route("/api/calc", methods=['POST'])
def add():
data = request.get_json()
if data is None:
return jsonify({ 'error': 'Missing input' }), 400
a = int(data.get('a', 0))
b = int(data.get('b', 0))
div = 'na'
if b != 0:
div = a/b
return jsonify({
"a" : a,
"b" : b,
"add" : a+b,
"multiply" : a*b,
"subtract" : a-b,
"divide" : div,
})
import app
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert b'Post JSON to /api/calc' == rv.data
def test_calc():
web = app.app.test_client()
rv = web.post('/api/calc', json={ 'a' : '10', 'b': '2' })
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
resp = rv.json
assert resp == {
"a" : 10,
"b" : 2,
"add" : 12,
"multiply" : 20,
"subtract" : 8,
"divide" : 5,
}
def test_bad_request():
web = app.app.test_client()
rv = web.post('/api/calc')
assert rv.status == '400 BAD REQUEST'
passlib
from passlib.hash import pbkdf2_sha256
import sys
if len(sys.argv) != 2:
exit("Usage: {} PASSWORD".format(sys.argv[0]))
pw = sys.argv[1]
hash1 = pbkdf2_sha256.hash(pw)
print(hash1)
hash2 = pbkdf2_sha256.hash(pw)
print(hash2)
print(pbkdf2_sha256.verify(pw, hash1))
print(pbkdf2_sha256.verify(pw, hash2))
$ python use_passlib.py "my secret"
$pbkdf2-sha256$29000$svZ.7z2HEEJIiVHqPeecMw$QAWd8P7MaPDXlEwtsv9AqhFEP2hp8MvZ9QxasIw4Pgw
$pbkdf2-sha256$29000$XQuh9N57r9W69x6jtDaG0A$VtD35zfeZhXsE/jxGl6wB7Mjwj/5iDGZv6QC7XBJjrI
True
True
Flask Testing
from flask import Flask, jsonify
myapp = Flask(__name__)
@myapp.route("/")
def main():
return '''
Main <a href="/add/23/19">add</a>
'''
@myapp.route("/add/<int:a>/<int:b>")
def api_info(a, b):
return str(a+b)
from app import myapp
import unittest
# python -m unittest test_app
class TestMyApp(unittest.TestCase):
def setUp(self):
self.app = myapp.test_client()
def test_main(self):
rv = self.app.get('/')
assert rv.status == '200 OK'
assert b'Main' in rv.data
#assert False
def test_add(self):
rv = self.app.get('/add/2/3')
self.assertEqual(rv.status, '200 OK')
self.assertEqual(rv.data, '5')
rv = self.app.get('/add/0/10')
self.assertEqual(rv.status, '200 OK')
self.assertEqual(rv.data, '10')
def test_404(self):
rv = self.app.get('/other')
self.assertEqual(rv.status, '404 NOT FOUND')
Flask Deploy app
from flask import Flask
myapp = Flask(__name__)
@myapp.route("/")
def main():
return 'Main'
{% embed include file="src/examples/flask/50/uwsgi.ini)
{% embed include file="src/examples/flask/50/nginx.conf)
Flask Simple Authentication + test
- HTTPBasicAuth
- flask_httpauth
- werkzeug.security
- generate_password_hash
- check_password_hash
from flask import Flask
from flask_httpauth import HTTPBasicAuth
from werkzeug.security import generate_password_hash, check_password_hash
app = Flask(__name__)
auth = HTTPBasicAuth()
users = {
"john": generate_password_hash("nhoj"),
"jane": generate_password_hash("enaj")
}
@app.route("/")
def hello():
return "Hello World!"
@auth.verify_password
def verify_password(username, password):
if username in users:
return check_password_hash(users.get(username), password)
return False
@app.route("/admin")
@auth.login_required
def admin():
return "Hello Admin"
import app
import base64
def test_app():
web = app.app.test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert rv.data == b'Hello World!'
def test_admin_unauth():
web = app.app.test_client()
rv = web.get('/admin')
assert rv.status == '401 UNAUTHORIZED'
assert rv.data == b'Unauthorized Access'
assert 'WWW-Authenticate' in rv.headers
assert rv.headers['WWW-Authenticate'] == 'Basic realm="Authentication Required"'
def test_admin_auth():
web = app.app.test_client()
credentials = base64.b64encode(b'john:nhoj').decode('utf-8')
rv = web.get('/admin', headers={
'Authorization': 'Basic ' + credentials
})
assert rv.status == '200 OK'
assert rv.data == b'Hello Admin'
Flask REST API
- [flask-restful](https://flask-restful.readthedocs.io/en/latest/quickstart.html" %}
Flask REST API - Echo
from flask import Flask, request
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class Echo(Resource):
def get(self):
return { "prompt": "Type in something" }
def post(self):
return { "echo": "This" }
api.add_resource(Echo, '/echo')
import api
def test_echo():
web = api.app.test_client()
rv = web.get('/echo')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {"prompt": "Type in something"}
rv = web.post('/echo')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {"echo": "This"}
Flask REST API - parameters in path
from flask import Flask, request
from flask_restful import Api, Resource
app = Flask(__name__)
api = Api(app)
class Echo(Resource):
def get(self, me):
return { "res": f"Text: {me}" }
def post(self, me):
return { "Answer": f"You said: {me}" }
api.add_resource(Echo, '/echo/<me>')
import api
def test_echo():
web = api.app.test_client()
rv = web.get('/echo/hello')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'res': 'Text: hello'}
rv = web.post('/echo/ciao')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'Answer': 'You said: ciao'}
Flask REST API - parameter parsing
from flask import Flask, request
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
class Echo(Resource):
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('text', help='Type in some text')
args = parser.parse_args()
return { "res": f"Text: {args['text']}" }
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('text', help='Type in some text')
args = parser.parse_args()
return { "Answer": f"You said: {args['text']}" }
api.add_resource(Echo, '/echo')
import api
def test_echo():
web = api.app.test_client()
rv = web.get('/echo?text=hello')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'res': 'Text: hello'}
rv = web.post('/echo', data={'text': 'ciao'})
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'Answer': 'You said: ciao'}
# If the parameter is missing the parser just returns None
rv = web.get('/echo')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'res': 'Text: None'}
Flask REST API - parameter parsing - required
from flask import Flask, request
from flask_restful import Api, Resource, reqparse
app = Flask(__name__)
api = Api(app)
class Echo(Resource):
def get(self):
parser = reqparse.RequestParser()
parser.add_argument('text', help='Type in some text', required=True)
args = parser.parse_args()
return { "res": f"Text: {args['text']}" }
def post(self):
parser = reqparse.RequestParser()
parser.add_argument('text', help='Type in some text')
args = parser.parse_args()
return { "Answer": f"You said: {args['text']}" }
api.add_resource(Echo, '/echo')
import api
def test_echo():
web = api.app.test_client()
rv = web.get('/echo?text=hello')
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'res': 'Text: hello'}
rv = web.post('/echo', data={'text': 'ciao'})
assert rv.status == '200 OK'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'Answer': 'You said: ciao'}
# If the parameter is missing the parser just returns None
rv = web.get('/echo')
assert rv.status == '400 BAD REQUEST'
assert rv.headers['Content-Type'] == 'application/json'
assert rv.json == {'message': {'text': 'Type in some text'}}
Login
from flask import Flask, request, redirect, url_for
app = Flask(__name__)
@app.route('/')
def index():
return '<a href="/login">login</a>'
@app.route('/login')
def login_get():
return '''<form action="/login" method="POST">
<input name="username">
<input name="password" type="password">
<input type="submit" value="Login">
</form>'''
@app.route('/login', methods=['POST'])
def login_post():
username = request.form.get('username')
password = request.form.get('password')
if password is None or username is None:
return redirect(url_for('login_get'))
if password == username: # do the real verification here
return redirect(url_for('user_page', name = username))
return redirect(url_for('login_get'))
@app.route('/user/')
def user_page_central():
return 'List of users'
@app.route('/user/<name>')
def user_page(name):
return f'Page of {name}'
Configuration
-
You can access and change it via the
app.config
dictionary in the applications code