Created
March 11, 2015 21:32
-
-
Save Flushot/840e549f055dce626dfb to your computer and use it in GitHub Desktop.
Python+flask+sqlalchemy microservice boilerplate
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
.PHONY: deps | |
all: deps | |
PYTHONPATH=.venv ; . .venv/bin/activate | |
.venv: | |
if [ ! -e ".venv/bin/activate_this.py" ] ; then virtualenv --clear .venv ; fi | |
deps: .venv | |
PYTHONPATH=.venv ; . .venv/bin/activate && .venv/bin/pip install -U -r requirements.txt | |
test: deps tests | |
. .venv/bin/activate && .venv/bin/python -m unittest discover | |
clean: | |
rm -rf .venv | |
rm -rf {*.egg-info,build} | |
rm -f `find . -name \*.pyc -print0 | xargs -0` |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
flask | |
flask-restless | |
flask-classy | |
sqlalchemy | |
Paste | |
argparse | |
pyopenssl | |
requests | |
formic==0.9beta8 | |
livereload |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import os | |
import sys | |
import json | |
import datetime | |
import logging | |
__version__ = '1.0.0' | |
log = logging.getLogger(__name__) | |
# Activate virtualenv (puts .venv dependencies in path) | |
if os.path.exists('.venv'): # and __name__ != '__main__': | |
import inspect | |
import site | |
log.info('Activating virtualenv...') | |
this_path = os.path.dirname(os.path.abspath(inspect.getfile(inspect.currentframe()))) | |
activate_this = os.path.realpath(os.path.join(this_path, '.venv/bin/activate_this.py')) | |
if not os.path.exists(activate_this): | |
sys.stderr.write("Missing dependencies. Please run 'make' before running this application.\n") | |
sys.exit(1) | |
execfile(activate_this, dict(__file__=activate_this)) | |
site.addsitedir(os.path.join(this_path, '.venv/lib/python2.6/site-packages')) | |
sys.path.insert(0, this_path) # Unsure if this is even necessary | |
import argparse | |
import requests | |
from flask import Flask, Blueprint, render_template, request, url_for | |
from flask.ext.classy import FlaskView, route | |
import flask.ext.restless | |
import sqlalchemy.ext.declarative | |
from sqlalchemy import ForeignKey, Column, String, Text, Integer, Boolean, DateTime | |
from sqlalchemy.orm import backref, relationship | |
app = application = Flask('myapp') | |
app.config['DEBUG'] = True | |
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite://' | |
############## | |
### Models ### | |
############## | |
Model = sqlalchemy.ext.declarative.declarative_base() | |
class User(Model): | |
__tablename__ = 'users' | |
id = Column(Integer, primary_key=True) | |
first_name = Column(String(length=255)) | |
last_name = Column(String(length=255)) | |
email = Column(String(length=255)) | |
bio = Column(Text) | |
class Address(Model): | |
__tablename__ = 'addresses' | |
id = Column(Integer, primary_key=True) | |
address = Column(String(length=255)) | |
city = Column(String(length=100)) | |
state = Column(String(length=50)) | |
zip_code = Column(String(length=15)) | |
user_id = Column(Integer, ForeignKey(User.id), nullable=False) | |
user = relationship(User, backref='addresses') | |
###################### | |
### REST Resources ### | |
###################### | |
# @app.route('/') | |
# def index(): | |
# return 'Hello!' | |
############### | |
# Boilerplate # | |
############### | |
# Initialize database | |
log.info('Connecting to database...') | |
engine = sqlalchemy.create_engine(app.config['SQLALCHEMY_DATABASE_URI'], convert_unicode=True) | |
db_session = sqlalchemy.orm.scoped_session( | |
sqlalchemy.orm.sessionmaker(autocommit=False, | |
autoflush=False, | |
bind=engine)) | |
# Register REST endpoints for SqlAlchemy models at /<tablename> | |
manager = flask.ext.restless.APIManager(app, session=db_session) | |
manager.create_api(User, methods=['GET', 'POST', 'DELETE']) | |
manager.create_api(Address, methods=['GET', 'POST', 'DELETE']) | |
# Add seed data | |
def seed_database(): | |
""" | |
Seeds the database. | |
""" | |
log.info('Seeding the database...') | |
user = User() | |
user.first_name = 'Chris' | |
user.last_name = 'Lyon' | |
user.email = '[email protected]' | |
db_session.add(user) | |
address = Address() | |
address.address = '1234 Main St' | |
address.city = 'Los Angeles' | |
address.state = 'CA' | |
address.zip = '90034' | |
user.addresses.append(address) | |
db_session.commit() | |
@app.teardown_appcontext | |
def shutdown_session(exception=None): | |
db_session.remove() | |
# Bootstrap | |
if __name__ == '__main__': | |
default_interface = '0.0.0.0' | |
default_port = 8000 | |
default_lr_port = 35729 | |
default_env = 'dev' | |
# Parse arguments | |
argp = argparse.ArgumentParser(description='Service controller') | |
argp.add_argument('--interface', '-i', default=default_interface, metavar='IP', | |
help='IP address to listen on (default: any)') | |
argp.add_argument('--port', '-p', default=default_port, metavar='PORT', | |
help='Port to listen on (default: %d)' % default_port) | |
argp.add_argument('--lr-port', default=default_lr_port, metavar='PORT', | |
help='Port for LiveReload server to listen on (default: %d)' % default_lr_port) | |
argp.add_argument('--env', '-e', default=os.environ.get('SERVICE_ENV', default_env), metavar='ENV_NAME', | |
help='Environment to use (default: SERVICE_ENV or "%s"' % default_env) | |
argp.add_argument('--ssl', '-s', action='store_true', | |
help='Enable SSL? If so, looks for X.509 certs in SERVICE_PUB_KEY and SERVICE_PRI_KEY') | |
argp.add_argument('--init-db', action='store_true', | |
help='Initialize the database') | |
args = argp.parse_args() | |
# Initialize logging | |
is_dev = (args.env == 'dev') | |
logging.basicConfig(level=logging.DEBUG if is_dev else logging.INFO, | |
format='%(asctime)s | %(levelname)-5s | %(name)s: %(message)s') | |
# Initialize the database | |
if args.init_db: | |
log.info('Initializing the database...') | |
Model.metadata.create_all(bind=engine) | |
seed_database() | |
# SSL support needed? | |
if args.ssl: | |
from OpenSSL import SSL | |
ssl_context = SSL.Context(SSL.SSLv23_METHOD) | |
ssl_context.use_publickey_file(os.environ['SERVICE_PUB_KEY']) | |
ssl_context.use_privatekey_file(os.environ['SERVICE_PRI_KEY']) | |
else: | |
ssl_context = None | |
# Start web server | |
log.info('Starting HTTP server on %s:%d...' % (args.interface, args.port)) | |
if is_dev: | |
import formic | |
import livereload | |
# LiveReload HTTP+TCP server (watches files for changes) | |
lr_server = livereload.Server(app.wsgi_app) | |
for path in formic.FileSet(directory='.', include=['**.py', '**.conf']): | |
lr_server.watch(path) | |
log.info('LiveReload listening on port %d' % args.lr_port) | |
lr_server.serve(host=args.interface, | |
port=args.port, | |
liveport=args.lr_port) | |
else: | |
import paste.httpserver | |
# Paste HTTP server (thread pool and handles load better) | |
paste.httpserver.serve(app, | |
host=args.interface, | |
port=args.port, | |
ssl_context=ssl_context, | |
use_threadpool=True) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment