Flask counter with SQLite, SQLAlchemy, pytest
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
Published on 2021-05-13