├── .gitignore ├── README.md ├── alembic.ini ├── alembic ├── env.py ├── script.py.mako └── versions │ └── 35b593d48d6a_user_models.py ├── basic_app ├── __init__.py ├── auth │ ├── __init__.py │ ├── models.py │ └── views.py └── config.py ├── manage.py └── requirements.txt /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | app.db 3 | .idea 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Basic Flask 2 | =========== 3 | 4 | This sample project shows how I organize a project to use Flask, 5 | Flask-SQLAlchemy, and Alembic together. It demonstrates the application 6 | factory pattern and is organized using blueprints. 7 | 8 | Demonstrating migration generation 9 | ---------------------------------- 10 | 11 | I used Alembic to autogenerate a migration and then tweaked the results. If you 12 | want to see autogeneration in action, just delete 13 | `alembic/versions/35b593d48d6a_user_models.py`, then run 14 | `alembic revision --autogenerate -m "user models"`. 15 | 16 | Set up the database 17 | ------------------- 18 | 19 | Change `basic_app.config.SQLALCHEMY_DATABASE_URI` to what you want. By default 20 | it points to a sqlite file called `app.db` in the project folder. 21 | 22 | Then run `alembic upgrade head` to set up the database through migrations. 23 | 24 | Or, permform the following from a Python shell. 25 | 26 | In [1]: from basic_app import create_app, db 27 | In [2]: create_app().app_context().push() 28 | In [3]: db.create_all() 29 | 30 | Then run `alembic stamp head` to mark migrations as current. 31 | 32 | Origin 33 | ------ 34 | 35 | Created in answer to this question on /r/flask and StackOverflow: 36 | 37 | * [\[AF\] Has anyone ever gotten Alembic to work with Flask Blueprints? \[SQLAlchemy\]](http://www.reddit.com/r/flask/comments/1h1k5g/af_has_anyone_ever_gotten_alembic_to_work_with/) 38 | * [Alembic autogenerates empty Flask-SQLAlchemy migrations](http://stackoverflow.com/questions/17201800/alembic-autogenerates-empty-flask-sqlalchemy-migrations) 39 | -------------------------------------------------------------------------------- /alembic.ini: -------------------------------------------------------------------------------- 1 | [alembic] 2 | script_location = alembic 3 | 4 | [loggers] 5 | keys = root,sqlalchemy,alembic 6 | 7 | [handlers] 8 | keys = console 9 | 10 | [formatters] 11 | keys = generic 12 | 13 | [logger_root] 14 | level = WARN 15 | handlers = console 16 | qualname = 17 | 18 | [logger_sqlalchemy] 19 | level = WARN 20 | handlers = 21 | qualname = sqlalchemy.engine 22 | 23 | [logger_alembic] 24 | level = INFO 25 | handlers = 26 | qualname = alembic 27 | 28 | [handler_console] 29 | class = StreamHandler 30 | args = (sys.stderr,) 31 | level = NOTSET 32 | formatter = generic 33 | 34 | [formatter_generic] 35 | format = %(levelname)-5.5s [%(name)s] %(message)s 36 | datefmt = %H:%M:%S 37 | -------------------------------------------------------------------------------- /alembic/env.py: -------------------------------------------------------------------------------- 1 | from logging.config import fileConfig 2 | import os 3 | import sys 4 | from alembic import context 5 | from sqlalchemy import engine_from_config, pool 6 | 7 | sys.path.append(os.getcwd()) 8 | 9 | from basic_app import db, create_app 10 | 11 | config = context.config 12 | 13 | fileConfig(config.config_file_name) 14 | 15 | app = create_app() 16 | config.set_main_option("sqlalchemy.url", app.config["SQLALCHEMY_DATABASE_URI"]) 17 | 18 | target_metadata = db.metadata 19 | 20 | 21 | def run_migrations_offline(): 22 | """Run migrations in 'offline' mode. 23 | 24 | This configures the context with just a URL 25 | and not an Engine, though an Engine is acceptable 26 | here as well. By skipping the Engine creation 27 | we don't even need a DBAPI to be available. 28 | 29 | Calls to context.execute() here emit the given string to the 30 | script output. 31 | 32 | """ 33 | 34 | url = config.get_main_option("sqlalchemy.url") 35 | context.configure(url=url) 36 | 37 | with context.begin_transaction(): 38 | context.run_migrations() 39 | 40 | 41 | def run_migrations_online(): 42 | """Run migrations in 'online' mode. 43 | 44 | In this scenario we need to create an Engine 45 | and associate a connection with the context. 46 | 47 | """ 48 | 49 | engine = engine_from_config( 50 | config.get_section(config.config_ini_section), 51 | prefix='sqlalchemy.', 52 | poolclass=pool.NullPool 53 | ) 54 | connection = engine.connect() 55 | context.configure(connection=connection, target_metadata=target_metadata) 56 | 57 | try: 58 | with context.begin_transaction(): 59 | context.run_migrations() 60 | finally: 61 | connection.close() 62 | 63 | if context.is_offline_mode(): 64 | run_migrations_offline() 65 | else: 66 | run_migrations_online() 67 | -------------------------------------------------------------------------------- /alembic/script.py.mako: -------------------------------------------------------------------------------- 1 | """${message} 2 | 3 | Revision ID: ${up_revision} 4 | Revises: ${down_revision} 5 | Create Date: ${create_date} 6 | """ 7 | 8 | from alembic import op 9 | import sqlalchemy as sa 10 | ${imports if imports else ""} 11 | # revision identifiers, used by Alembic. 12 | revision = ${repr(up_revision)} 13 | down_revision = ${repr(down_revision)} 14 | 15 | 16 | def upgrade(): 17 | ${upgrades if upgrades else "pass"} 18 | 19 | 20 | def downgrade(): 21 | ${downgrades if downgrades else "pass"} 22 | -------------------------------------------------------------------------------- /alembic/versions/35b593d48d6a_user_models.py: -------------------------------------------------------------------------------- 1 | """user models 2 | 3 | Revision ID: 35b593d48d6a 4 | Revises: None 5 | Create Date: 2013-06-25 10:10:53.866281 6 | """ 7 | 8 | from alembic import op 9 | import sqlalchemy as sa 10 | from sqlalchemy.sql import expression 11 | 12 | # revision identifiers, used by Alembic. 13 | revision = "35b593d48d6a" 14 | down_revision = None 15 | 16 | 17 | def upgrade(): 18 | op.create_table("group", 19 | sa.Column("id", sa.Integer, primary_key=True), 20 | sa.Column("name", sa.String(100), nullable=False, unique=True, index=True) 21 | ) 22 | op.create_table("user", 23 | sa.Column("id", sa.Integer), 24 | sa.Column("username", sa.String(254), nullable=False, unique=True, index=True), 25 | sa.Column("password", sa.String(60)), 26 | sa.Column("name", sa.String(254), nullable=False, server_default=""), 27 | sa.Column("email", sa.String(254), nullable=False, server_default=""), 28 | sa.Column("active", sa.Boolean, nullable=False, server_default=expression.true()), 29 | sa.PrimaryKeyConstraint("id") 30 | ) 31 | op.create_table("user_group", 32 | sa.Column("user_id", sa.Integer, sa.ForeignKey("user.id"), primary_key=True), 33 | sa.Column("group_id", sa.Integer, sa.ForeignKey("group.id"), primary_key=True), 34 | ) 35 | 36 | 37 | def downgrade(): 38 | op.drop_table("user_group") 39 | op.drop_table("user") 40 | op.drop_table("group") 41 | -------------------------------------------------------------------------------- /basic_app/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Flask 2 | from flask.ext.script import Manager 3 | from flask.ext.sqlalchemy import SQLAlchemy 4 | from basic_app import config 5 | 6 | db = SQLAlchemy() 7 | 8 | 9 | def load_models(): 10 | from basic_app.auth import models 11 | 12 | load_models() 13 | 14 | 15 | def init_extensions(app): 16 | db.init_app(app) 17 | 18 | 19 | def init_views(app): 20 | from basic_app import auth 21 | 22 | app.register_blueprint(auth.bp, url_prefix="/auth") 23 | 24 | 25 | def create_app(config=config): 26 | app = Flask(__name__) 27 | app.config.from_object(config) 28 | 29 | init_extensions(app) 30 | init_views(app) 31 | 32 | return app 33 | 34 | manager = Manager(create_app) 35 | -------------------------------------------------------------------------------- /basic_app/auth/__init__.py: -------------------------------------------------------------------------------- 1 | from flask import Blueprint 2 | 3 | bp = Blueprint("auth", __name__, template_folder="templates") 4 | 5 | from basic_app.auth import views 6 | -------------------------------------------------------------------------------- /basic_app/auth/models.py: -------------------------------------------------------------------------------- 1 | from sqlalchemy.sql import expression 2 | from basic_app import db 3 | 4 | 5 | class Group(db.Model): 6 | id = db.Column(db.Integer, primary_key=True) 7 | name = db.Column(db.String(100), nullable=False, unique=True, index=True) 8 | 9 | 10 | class User(db.Model): 11 | id = db.Column(db.Integer, primary_key=True) 12 | username = db.Column(db.String(254), nullable=False, unique=True, index=True) 13 | password = db.Column(db.String(60)) 14 | name = db.Column(db.String(254), nullable=False, default="", server_default="") 15 | email = db.Column(db.String(254), nullable=False, default="", server_default="") 16 | active = db.Column(db.Boolean, nullable=False, default=False, server_default=expression.true()) 17 | 18 | groups = db.relationship(Group, lambda: user_group, backref=db.backref("users")) 19 | 20 | user_group = db.Table("user_group", 21 | db.Column("user_id", db.Integer, db.ForeignKey(User.id), primary_key=True), 22 | db.Column("group_id", db.Integer, db.ForeignKey(Group.id), primary_key=True) 23 | ) 24 | -------------------------------------------------------------------------------- /basic_app/auth/views.py: -------------------------------------------------------------------------------- 1 | from basic_app.auth import bp 2 | from basic_app.auth.models import User 3 | 4 | 5 | @bp.route("/") 6 | def hello(username): 7 | u = User.query.filter_by(username=username).first_or_404() 8 | return "Hello, {}!".format(u.username) 9 | -------------------------------------------------------------------------------- /basic_app/config.py: -------------------------------------------------------------------------------- 1 | DEBUG = True 2 | SECRET_KEY = "dev" 3 | 4 | SQLALCHEMY_DATABASE_URI = "sqlite:///app.db" 5 | -------------------------------------------------------------------------------- /manage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from basic_app import manager 3 | 4 | manager.run() 5 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Flask-Script 2 | Flask-SQLAlchemy 3 | alembic 4 | --------------------------------------------------------------------------------