This is a counter example to show how to create a web application using Python Flask with SQLite database. Using SQLAlchemy as the ORM and ensuring that we can test the whole application using pytest. Making sure that each test-case has its own database.
Directory layout
.
├── app.py
├── model.py
├── templates
│ └── counter.html
└── test_app.py
To run the application execute the following:
FLASK_APP=app FLASK_DEBUG=1 flask run
The application
examples/flask/sqlite-counter/app.py
from flask import Flask, render_template, request, abort
import logging
import os
from model import db, Counter
def create_app():
app = Flask(__name__)
app.logger.setLevel(logging.INFO)
with app.app_context():
db_file = os.environ.get('COUNT_DB') or 'counter.db'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + db_file
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
db.init_app(app)
db.create_all()
@app.route('/')
@app.route('/<name>')
def counter(name = None):
if name:
if name == 'favicon.ico':
return abort(404)
app.logger.info("name %s", name)
cntr = Counter.query.filter_by(name=name).first()
app.logger.info(cntr)
if not cntr:
cntr = Counter(name=name, count=1)
db.session.add(cntr)
else:
cntr.count += 1
db.session.commit()
app.logger.info(cntr)
return render_template('counter.html', name = name, counter = cntr.count)
else:
counters = [ {"name": counter.name, "count" : counter.count} for counter in Counter.query.all()]
app.logger.info(counters)
return render_template('counter.html', counters = counters)
return app
The model - the SQLAlchemy configuration
examples/flask/sqlite-counter/model.py
from flask_sqlalchemy import SQLAlchemy
db = SQLAlchemy()
class Counter(db.Model):
name = db.Column(db.String(80), primary_key=True, unique=True, nullable=False)
count = db.Column(db.Integer, unique=False, nullable=False, default=0)
The HTML template using Jinja
examples/flask/sqlite-counter/templates/counter.html
<h1>Counter</h1>
{% if name %}
{{ name }} : {{ counter}}
{% else %}
{% if counters %}
<ul>
{% for counter in counters %}
<li><a href="/{{counter.name}}">{{counter.name}} {{counter.count}}</a></li>
{% endfor %}
</ul>
{% else %}
Try <a href="/something">something</a>
{% endif %}
{% endif %}
The tests
The second test function was added primarily to show that the test functions have their own separate databases. and there is no interence between the test functions.
examples/flask/sqlite-counter/test_app.py
from app import create_app
import os
def test_app(tmpdir):
os.environ['COUNT_DB'] = str(os.path.join(tmpdir, "test.db"))
print(os.environ['COUNT_DB'])
web = create_app().test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert '<h1>Counter</h1>' in rv.data.decode('utf-8')
assert '<a href="/something">something</a>' in rv.data.decode('utf-8')
rv = web.get('/blue')
assert rv.status == '200 OK'
#print(rv.data)
assert '<h1>Counter</h1>' in rv.data.decode('utf-8')
assert 'blue : 1' in rv.data.decode('utf-8')
rv = web.get('/')
assert rv.status == '200 OK'
assert '<h1>Counter</h1>' in rv.data.decode('utf-8')
assert '<a href="/blue">blue 1</a>' in rv.data.decode('utf-8')
assert 'something' not in rv.data.decode('utf-8')
rv = web.get('/blue')
assert rv.status == '200 OK'
#print(rv.data)
assert '<h1>Counter</h1>' in rv.data.decode('utf-8')
assert 'blue : 2' in rv.data.decode('utf-8')
assert 'red' not in rv.data.decode('utf-8')
assert 'something' not in rv.data.decode('utf-8')
rv = web.get('/red')
assert rv.status == '200 OK'
#print(rv.data)
assert '<h1>Counter</h1>' in rv.data.decode('utf-8')
#assert 'blue : 2' in rv.data.decode('utf-8')
assert 'red : 1' in rv.data.decode('utf-8')
rv = web.get('/')
assert rv.status == '200 OK'
assert '<h1>Counter</h1>' in rv.data.decode('utf-8')
assert '<a href="/blue">blue 2</a>' in rv.data.decode('utf-8')
assert '<a href="/red">red 1</a>' in rv.data.decode('utf-8')
assert 'something' not in rv.data.decode('utf-8')
# Verify that the two test-cases indeed use different databases
def test_app_clean_db(tmpdir):
os.environ['COUNT_DB'] = str(os.path.join(tmpdir, "test.db"))
print(os.environ['COUNT_DB'])
web = create_app().test_client()
rv = web.get('/')
assert rv.status == '200 OK'
assert '<h1>Counter</h1>' in rv.data.decode('utf-8')
assert '<a href="/something">something</a>' in rv.data.decode('utf-8')
assert 'blue' not in rv.data.decode('utf-8')
assert 'red' not in rv.data.decode('utf-8')
You can run the tests by
pytest