Exercising the Mega-flask Tutorial. The following notes is a quick summary for future reference. I was not particularly interesting in serving client UI from the server, so these are linked directly to the original post.
- Chapter 01: scaffold and run bare bone app.
- Chapter 02: covers flask html templates rendering and inheritance. original post
- Chapter 03: web forms, add configurations to a flask app.
- Chapter 04 : databases.
This chapter starts from scratch making sure that python 3.4 or higher is installed on the machine.
Summary:
- run
$ python3
from command line $ mkdir microblog && cd microblog
$ python3 -m venv venv
(for earlier than 3.4 virtualenv, then$ viraulenv evn
$ source venv/bin/activate
(when venv is active, python interpreter is invoked withpython
notpython3
) (opening the project in PyCharm after this point should be fine. venv will be set as the Project Interpreter automatically)(venv) $ pip install flask
- create
app
package to serve the app:(venv) $ mkdir app && cd app
- add the
__init__.py
:
# app/__init__.py
form flask import Flask
app = Flask(__name__)
from app import routes # added at the end to avoid circular import. (routes.py will import app)
- add
routes.py
inside theapp
package. It will hold View functions.
#app/routes.py: Home page route
from app import app
@app.route('/')
@app.route('/index')
def index():
return "Hello, World!"
- To complete the application, you need to have a Python script at the top-level that defines the Flask application instance. We will call it
microblog.py
:
# microblog.py
from app import app
- project structure should look like:
microblog/
venv/
app/
__init__.py
routes.py
microblog.py
- set env var
FLASK_APP
and callflask run
:
(venv) $ export FLASK_APP=microblog.py
(venv) $ flask run
Highlights to remember
- Using a top-level module
config.py
to handle configurations by defining a classConfig
that holds on to some class variables. This approach is extensible by subclassingConfig
. (remember to add the config to the flask app after creating its instance by callingapp.config.from_object(Config)
) - security tip: protect against Cross-Site Request Forgery CSRF (pronounced "seasurf"): The package used to handle web forms
Flask-WTF
offers an automatic protection (token generation and inclusion) given two pre-requisites:- Having a
SECRET_KEY
item in the app's config - adding
{{ form.hidden_tag() }}
to the html template and inside the container<form>
tag.
- Having a
- Some helpfull methods from flask like:
flash()
which queues a string to flashed msgs. Flash messages are retrieved by callingget_flashed_messages()
. After this call returns, messages are de-queued and will not be returned on subsequent calls. Usuallyget_flashed_messages()
is called within the base html template which gives a global rendering of flash messages.redirect('path')
url_for('login')
returns the path associated with the View function namedlogin
- Flask does not support databases natively, it is intentionally not opinionated in this area.
- The search should be for a db in python, that also offers a flask extension.
- SQLAlchemy is an ORM with a flask extension: Flask-SQLAlchemy
- SQLAlchemy supports a long list of database engines, including MySQL, PostgreSQL, and SQLite.
- Alembic , is a database migration tool for SQLAlchemy.
- Flask-Migrate is a flask extension that provides a small wrapper around alembic.
(venv) $ pip install flask-sqlalchemy
The extension looks for the following variables in the app's configurations:
SQLALCHEMY_DATABASE_URI
should hold the location to the db.SQLALCHEMY_TRACK_MODIFICATIONS
(usually set toFalse
more info )- create
db
andmigrate
instances in app's package:
# in app/__init.py__
...
app = Flask(__name__)
...
db = SQLAlchemy(app)
migrate = Migrate(app, db)
(Note the pattern in initializing flask extensions. Passing the flask app instance to a constructor)
- create a new module under
app/
and call it:models
. This SHOULD be imported inapp/__init__.py
(by the end of file, just likeroutes
) - define some db model classes
- for more info on defining model classes and interacting with them check out the SQLAlchemy_notes page.
Again, Flask-Migrate is a small wrapper around Almebic
(venv) $ pip install flask-migrate
Alembic requires a directory to store all migration scripts, plus some additional files for its configurations and for creating new migrations.
(venv) $ export FLASK_APP=microblog.py # if needed
(venv) $ flask db init
This will create a new directory in the project's root called migrations
with the structure:
microblog/
...
migrations/
version/ # a directory where all migration scripts reside
alembic.ini
env.py
README
script.py.mako # a template file used to create new migration scripts
Migrations files are python modules. A migration can be expressed using Alembic and SqlAlchemy classes. Alternatively, Alembic provides means to write the SQL code manually.
Alembic can auto generate the migration files from model classes. For example: at this point of the tutorial, running the following will generate the script that eventually defines the DDL for the users table:
(venv) $ flask db migrate -m "users table"
this will be the output:
# in migrations/versions/8635f4295237_users_table.py
"""users table
Revision ID: 8635f4295237
Revises:
Create Date: 2018-02-27 19:13:11.641464
"""
from alembic import op
import sqlalchemy as sa
# revision identifiers, used by Alembic.
revision = '8635f4295237'
down_revision = None
branch_labels = None
depends_on = None
def upgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.create_table('user',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('username', sa.String(length=64), nullable=True),
sa.Column('email', sa.String(length=120), nullable=True),
sa.Column('password_hash', sa.String(length=128), nullable=True),
sa.PrimaryKeyConstraint('id')
)
op.create_index(op.f('ix_user_email'), 'user', ['email'], unique=True)
op.create_index(op.f('ix_user_username'), 'user', ['username'], unique=True)
# ### end Alembic commands ###
def downgrade():
# ### commands auto generated by Alembic - please adjust! ###
op.drop_index(op.f('ix_user_username'), table_name='user')
op.drop_index(op.f('ix_user_email'), table_name='user')
op.drop_table('user')
# ### end Alembic commands ###
Each migration version has an auto-generated revision code. Alembic keeps track of the last applied revision. After adding a new revision, it must be applied
(venv) $ flask db upgrade
you can add the following to microblog.py
so that when flask shell
is executed, and interpreter will be loaded with some pre-defined imports
# in microblog.py
...
from app.models import User, Post
@app.shell_context_processor
def make_shell_context():
return {'db': db, 'User': User, 'Post': Post}