├── README.md └── src ├── app.py ├── models.py └── handlers.py /README.md: -------------------------------------------------------------------------------- 1 | This is an example of one way to use sqlalchemy with tornado. 2 | -------------------------------------------------------------------------------- /src/app.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | :file: tornado-sqlalchemy/app.py 4 | :author: husobee 5 | :date: 2014-05-13 6 | :license: MIT License, 2014 7 | 8 | Small example of using sqlalchemy with tornado, with an executor, to not block up ioloop 9 | 10 | """ 11 | from tornado.options import define, options, parse_command_line # tornado options related imports 12 | 13 | define("port", default=8888, help="Port for webserver to run") # need the port to run on 14 | define("db_connection_str", default="sqlite:///default.sqlite.db", help="Database connection string for application") # db connection string 15 | define("executor_max_threads", default=20, help="max threads for threadpool executor") # max number of threads for executor 16 | 17 | parse_command_line() # firstly, get the above options from command line 18 | 19 | from tornado import ioloop, web 20 | from handlers import MainHandler # this is our main handler 21 | 22 | from sqlalchemy import create_engine 23 | from sqlalchemy.orm import sessionmaker 24 | 25 | from models import Base as models_base # get the declared sqlalchemy base 26 | 27 | db_engine = create_engine(options.db_connection_str) # setup db engine for sqlalchemy 28 | db_session = sessionmaker() # setup the session maker for sqlalchemy 29 | 30 | class MyApplication(web.Application): 31 | """ Inhierited tornado.web.Application - stowing some sqlalchemy session information in the application """ 32 | def __init__(self, *args, **kwargs): 33 | """ setup the session to engine linkage in the initialization """ 34 | self.session = kwargs.pop('session') 35 | self.session.configure(bind=db_engine) 36 | super(MyApplication, self).__init__(*args, **kwargs) 37 | 38 | def create_database(self): 39 | """ this will create a database """ 40 | models_base.metadata.create_all(db_engine) 41 | 42 | application = MyApplication([ # here is our url/handler mappings 43 | (r"/([^/]*)", MainHandler, dict(db_session=db_session)), # main handler takes a url parm, and we are passing session to initialize 44 | ], session=db_session) 45 | 46 | if __name__ == "__main__": # main 47 | application.create_database() # create the database 48 | application.listen(options.port) # listen on the right port 49 | ioloop.IOLoop.instance().start() # startup ioloop 50 | 51 | 52 | -------------------------------------------------------------------------------- /src/models.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | :file: tornado-sqlalchemy/models.py 4 | :author: husobee 5 | :date: 2014-05-13 6 | :license: MIT License, 2014 7 | 8 | Models for the tornado application 9 | """ 10 | from sqlalchemy import Column, Integer, String 11 | from sqlalchemy.ext.declarative import declarative_base 12 | from tornado.concurrent import return_future, run_on_executor 13 | from tornado.ioloop import IOLoop 14 | from tornado.options import options 15 | from concurrent.futures import ThreadPoolExecutor 16 | import json 17 | 18 | Base = declarative_base() # declaritive base for sqlalchemy 19 | 20 | EXECUTOR = ThreadPoolExecutor(options.executor_max_threads) # setup global executor 21 | 22 | class AnOrm(Base): 23 | """ Sqlalchemy ORM, mapping ot table an_orm """ 24 | __tablename__ = 'an_orm' 25 | 26 | id = Column(Integer, primary_key=True) # has an int id 27 | name = Column(String) # has a name 28 | description = Column(String) # has a description 29 | 30 | def to_json(self): # hrm, serializing to_json 31 | return(json.dumps({ 'name': self.name, 'description':self.description })) #indeed 32 | 33 | class AnOrmAsyncModel(object): # this model will manipulate the AnOrm instances 34 | def __init__(self, db_session, io_loop=None, executor=EXECUTOR): # initial setup as an executor 35 | self.io_loop = io_loop or IOLoop.instance() # grabe the ioloop or the global instance 36 | self.executor = executor # grab the executor 37 | self.db_session = db_session # get the session 38 | 39 | @run_on_executor # okay, run this method on the instance's executor 40 | @return_future # return a future 41 | def get_by_id(self, id:int, callback=None): 42 | """ AnOrmAsyncModel.get_by_id - Get AnOrm based on the ID from database """ 43 | session = self.db_session() # setup the session in this thread 44 | result = None 45 | try: 46 | result = session.query(AnOrm).filter(AnOrm.id==id).one() # do the request 47 | except Exception as e: 48 | result = e 49 | session.close() # close the session 50 | callback(result) # return results by calling callback 51 | 52 | @run_on_executor # run this method on the instance's executor 53 | @return_future # return a future 54 | def create(self, an_orm, callback=None): 55 | """ AnOrmAsyncModel.create - Create an orm in the database """ 56 | session = self.db_session() # setup session in this thread 57 | success = True 58 | try: 59 | session.add(an_orm) # add the orm to session 60 | session.commit() # commit the session when added 61 | except Exception as e: # if there was an exception 62 | session.rollback() # roll this back 63 | success = e # return that it failed 64 | session.close() # close the session 65 | callback(success) # return if it was success or not 66 | -------------------------------------------------------------------------------- /src/handlers.py: -------------------------------------------------------------------------------- 1 | """ 2 | 3 | :file: tornado-sqlalchemy/handlers.py 4 | :author: husobee 5 | :date: 2014-05-13 6 | :license: MIT License, 2014 7 | 8 | Handlers for the tornado application 9 | 10 | """ 11 | 12 | from tornado import web, gen 13 | from sqlalchemy.orm.exc import NoResultFound 14 | from models import AnOrmAsyncModel, AnOrm 15 | import json 16 | 17 | class MainHandler(web.RequestHandler): 18 | """ MainHandler - Handle the root url, implemented post and get""" 19 | 20 | def initialize(self, db_session): 21 | """ MainHandler.initialize - Setup the Models we will need for this handler """ 22 | self.an_orm_model = AnOrmAsyncModel(db_session) 23 | 24 | @web.asynchronous # this will be async 25 | @gen.coroutine # we will be using coroutines, with gen.Task 26 | def get(self, id): 27 | """ MainHandler.get - GET http method """ 28 | value = yield gen.Task(self.an_orm_model.get_by_id, int(id)) # take the id, int it, and get by id from model 29 | try: 30 | if isinstance(value.result(), Exception): # okay, future turned out to be an exception 31 | raise value.result() # raise it 32 | except NoResultFound as nrf: # is it a no results found exception? 33 | self.set_status(404) # then 404 34 | self.write("Not Found") 35 | except Exception as e: # uncaught other exception 36 | self.set_status(500) # we failed 37 | self.write("Internal Server Error") 38 | else: # no exception 39 | if isinstance(value.result(), AnOrm): # is it the thing we expected back? 40 | self.set_status(200) # good! 41 | self.write(value.result().to_json()) # return it 42 | else: 43 | self.set_status(500) # oh shit. 44 | self.write("Internal Server Error") 45 | self.finish() # finish the async response 46 | 47 | @web.asynchronous # this will be async 48 | @gen.coroutine # we will be using coroutine, with gen.Task 49 | def post(self, id): 50 | """ MainHandler.post - POST http method """ 51 | posted_data = json.loads(self.request.body.decode('utf-8')) # get the body from the request 52 | value = AnOrm(name=posted_data.get('name'), description=posted_data.get('description')) # populate an ORM with it 53 | done = yield gen.Task(self.an_orm_model.create, value) # do the create, wait for it to finish 54 | try: 55 | if isinstance(done.result(), Exception): # check if exception 56 | raise done.result() # raise it 57 | except Exception as e: #catch all 58 | self.set_status(500) # write internal server fail 59 | self.write("Internal Server Error") 60 | else: # no exception 61 | if isinstance(done.result(), bool): # check if the result is the correct type 62 | self.set_status(200) # yay! 63 | self.write(json.dumps(done.result())) #write the response 64 | else: 65 | self.set_status(500) # oh shit. 66 | self.write("Internal Server Error") 67 | self.finish() # finish the async response 68 | --------------------------------------------------------------------------------