Permalink
Browse files

Update application code for Heroku + SQLAlchemy

Updated to accomodate new logging configuration (stdout),
command line options, and to be able to provide sqlalchemy
sessions for database calls.
  • Loading branch information...
jiffyclub committed Jul 18, 2017
1 parent 50c5cf6 commit ca35047922cdc01546f52f671a99d25c1598ef4d
Showing with 118 additions and 80 deletions.
  1. +81 −52 app/app.py
  2. +37 −28 app/test/test_app.py
@@ -1,14 +1,17 @@
import contextlib
import json
import logging
import os
import jsonschema
import sqlalchemy as sa
import tornado.ioloop
import tornado.log
import tornado.options
import tornado.web
from ipythonblocks import BlockGrid
from sqlalchemy.orm import sessionmaker
from twiggy import log
# local imports
@@ -17,19 +20,13 @@
from .colorize import colorize
from .twiggy_setup import twiggy_setup
tornado.options.define('tornado_log_file',
default='/var/log/ipborg/tornado.log',
type=str)
tornado.options.define('app_log_file',
default='/var/log/ipborg/app.log',
type=str)
tornado.options.parse_command_line()
tornado.options.define('port', default=80, type=int)
tornado.options.define('db_url', type=str)
log = log.name(__name__)
def configure_tornado_logging():
fh = logging.handlers.RotatingFileHandler(
tornado.options.options.tornado_log_file,
maxBytes=2**29, backupCount=10)
fh = logging.StreamHandler()
fmt = logging.Formatter('%(asctime)s:%(levelname)s:%(name)s:%(message)s')
fh.setFormatter(fmt)
@@ -41,14 +38,6 @@ def configure_tornado_logging():
tornado.log.enable_pretty_logging(logger=logger)
settings = {
'static_path': os.path.join(os.path.dirname(__file__), 'static'),
'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
'debug': True,
'gzip': True
}
class MainHandler(tornado.web.StaticFileHandler):
def parse_url_path(self, url_path):
return 'main.html'
@@ -59,7 +48,21 @@ def parse_url_path(self, url_path):
return 'about.html'
class PostHandler(tornado.web.RequestHandler):
class DBAccessHandler(tornado.web.RequestHandler):
@contextlib.contextmanager
def session_context(self):
session = self.application.session_factory()
try:
yield session
session.commit()
except:
session.rollback()
raise
finally:
session.close()
class PostHandler(DBAccessHandler):
def post(self):
try:
req_data = json.loads(self.request.body)
@@ -73,7 +76,8 @@ def post(self):
log.debug('Post JSON validation failed.')
raise tornado.web.HTTPError(400, 'Post JSON validation failed.')
hash_id = dbi.store_grid_entry(req_data)
with self.session_context() as session:
hash_id = dbi.store_grid_entry(session, req_data)
if req_data['secret']:
url = 'http://ipythonblocks.org/secret/{}'
@@ -85,33 +89,37 @@ def post(self):
self.write({'url': url})
class GetGridSpecHandler(tornado.web.RequestHandler):
class GetGridSpecHandler(DBAccessHandler):
def initialize(self, secret):
self.secret = secret
def get(self, hash_id):
grid_spec = dbi.get_grid_entry(hash_id, self.secret)
if not grid_spec:
raise tornado.web.HTTPError(404, 'Grid not found.')
with self.session_context() as session:
grid_spec = dbi.get_grid_entry(session, hash_id, self.secret)
if not grid_spec:
raise tornado.web.HTTPError(404, 'Grid not found.')
self.write(grid_spec['grid_data'])
self.write(grid_spec.grid_data)
class RandomHandler(tornado.web.RequestHandler):
class RandomHandler(DBAccessHandler):
def get(self):
hash_id = dbi.get_random_hash_id()
with self.session_context() as session:
hash_id = dbi.get_random_hash_id(session)
log.info('redirecting to url /{0}', hash_id)
self.redirect('/' + hash_id, status=303)
class ErrorHandler(tornado.web.RequestHandler):
class ErrorHandler(DBAccessHandler):
def get(self):
self.send_error(404)
def write_error(self, status_code, **kwargs):
if status_code == 404:
self.render('404.html')
else:
super(ErrorHandler, self).send_error(status_code, **kwargs)
super().send_error(status_code, **kwargs)
class RenderGridHandler(ErrorHandler):
@@ -120,38 +128,59 @@ def initialize(self, secret):
@tornado.web.removeslash
def get(self, hash_id):
grid_spec = dbi.get_grid_entry(hash_id, secret=self.secret)
if not grid_spec:
self.send_error(404)
return
with self.session_context() as session:
grid_spec = dbi.get_grid_entry(session, hash_id, secret=self.secret)
gd = grid_spec['grid_data']
grid = BlockGrid(gd['width'], gd['height'], lines_on=gd['lines_on'])
grid._load_simple_grid(gd['blocks'])
grid_html = grid._repr_html_()
if not grid_spec:
self.send_error(404)
return
code_cells = grid_spec['code_cells'] or []
code_cells = [colorize(c) for c in code_cells]
gd = grid_spec.grid_data
grid = BlockGrid(gd['width'], gd['height'], lines_on=gd['lines_on'])
grid._load_simple_grid(gd['blocks'])
grid_html = grid._repr_html_()
self.render('grid.html', grid_html=grid_html, code_cells=code_cells)
code_cells = grid_spec.code_cells or []
code_cells = [colorize(c) for c in code_cells]
self.render('grid.html', grid_html=grid_html, code_cells=code_cells)
class AppWithSession(tornado.web.Application):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.engine = sa.create_engine(tornado.options.options.db_url)
self.session_factory = sessionmaker(bind=self.engine)
SETTINGS = {
'static_path': os.path.join(os.path.dirname(__file__), 'static'),
'template_path': os.path.join(os.path.dirname(__file__), 'templates'),
'debug': True,
'gzip': True
}
application = tornado.web.Application([
(r'/()', MainHandler, {'path': settings['template_path']}),
(r'/(about)', AboutHandler, {'path': settings['template_path']}),
(r'/random', RandomHandler),
(r'/post', PostHandler),
(r'/get/(\w{6}\w*)', GetGridSpecHandler, {'secret': False}),
(r'/get/secret/(\w{6}\w*)', GetGridSpecHandler, {'secret': True}),
(r'/(\w{6}\w*)/*', RenderGridHandler, {'secret': False}),
(r'/secret/(\w{6}\w*)/*', RenderGridHandler, {'secret': True}),
(r'/.*', ErrorHandler)
], **settings)
def make_application():
return AppWithSession(handlers=[
(r'/()', MainHandler, {'path': SETTINGS['template_path']}),
(r'/(about)', AboutHandler, {'path': SETTINGS['template_path']}),
(r'/random', RandomHandler),
(r'/post', PostHandler),
(r'/get/(\w{6}\w*)', GetGridSpecHandler, {'secret': False}),
(r'/get/secret/(\w{6}\w*)', GetGridSpecHandler, {'secret': True}),
(r'/(\w{6}\w*)/*', RenderGridHandler, {'secret': False}),
(r'/secret/(\w{6}\w*)/*', RenderGridHandler, {'secret': True}),
(r'/.*', ErrorHandler)
], **SETTINGS)
if __name__ == '__main__':
tornado.options.parse_command_line()
configure_tornado_logging()
twiggy_setup()
application.listen(8877)
log.fields(port=tornado.options.options.port).info('starting server')
application = make_application()
application.listen(tornado.options.options.port)
tornado.ioloop.IOLoop.instance().start()
@@ -2,32 +2,31 @@
import os
import tempfile
import dataset
import pytest
import sqlalchemy as sa
import testing.postgresql
import tornado.options
import tornado.testing
import mock
from sqlalchemy.orm import sessionmaker
from .. import app
from .. import dbinterface as dbi
from .. import models
def setup_module(module):
tornado.options.options.public_salt = 'public'
tornado.options.options.secret_salt = 'secret'
dbi.get_memcached().flush_all()
module.PG_FACTORY = testing.postgresql.PostgresqlFactory(
cache_initialized_db=True)
def teardown_module(module):
dbi.get_memcached().flush_all()
def setup_function(function):
_, tornado.options.options.db_file = tempfile.mkstemp()
module.PG_FACTORY.clear_cache()
def teardown_function(function):
os.remove(tornado.options.options.db_file)
@pytest.fixture(autouse=True)
def set_salts(monkeypatch):
monkeypatch.setenv('HASHIDS_PUBLIC_SALT', 'public')
monkeypatch.setenv('HASHIDS_SECRET_SALT', 'secret')
def data_2x2():
@@ -53,24 +52,30 @@ def request():
class UtilBase(tornado.testing.AsyncHTTPTestCase):
def setup_method(self, method):
_, tornado.options.options.db_file = tempfile.mkstemp()
self.postgresql = PG_FACTORY()
self.engine = sa.create_engine(self.postgresql.url())
models.Base.metadata.create_all(bind=self.engine)
self.Session = sessionmaker(bind=self.engine)
self.session = self.Session()
tornado.options.options.db_url = self.postgresql.url()
def teardown_method(self, method):
os.remove(tornado.options.options.db_file)
self.session.close()
self.Session.close_all()
self.engine.dispose()
self.postgresql.stop()
def get_app(self):
return app.application
return app.make_application()
def get_response(self, body=None):
self.http_client.fetch(
self.get_url(self.app_url), self.stop,
method=self.method, body=body)
return self.wait()
return self.fetch(self.app_url, method=self.method, body=body)
def save_grid(self, secret):
req = request()
req['secret'] = secret
hash_id = dbi.store_grid_entry(req)
hash_id = dbi.store_grid_entry(self.session, req)
self.session.commit()
return hash_id
@@ -115,9 +120,13 @@ def test_stores_data(self):
body = json.loads(response.body)
hash_id = body['url'].split('/')[-1]
grid_spec = dbi.get_grid_entry(hash_id)
del grid_spec['id']
assert grid_spec == json.loads(json.dumps(req))
grid_spec = dbi.get_grid_entry(self.session, hash_id)
assert grid_spec.id == 1
comp_data= json.loads(json.dumps(req))
for key, value in comp_data.items():
assert getattr(grid_spec, key) == value
class TestGetGrid(UtilBase):
@@ -175,14 +184,14 @@ def test_render(self):
response = self.get_response()
assert response.code == 200
assert '<table' in response.body
assert 'asdf' in response.body
assert b'<table' in response.body
assert b'asdf' in response.body
def test_render_secret(self):
hash_id = self.save_grid(True)
self.app_url = '/secret/{}'.format(hash_id)
response = self.get_response()
assert response.code == 200
assert '<table' in response.body
assert 'asdf' in response.body
assert b'<table' in response.body
assert b'asdf' in response.body

0 comments on commit ca35047

Please sign in to comment.