def test_manager_table_schema(self): post_callback = partial(self.validate_post, expected={ "id": 1, "data": { "a": "integer", "b": "string" } }) message = { "id": 1, "name": "table1", "cmd": "table_method", "method": "schema", "args": [False] } manager = PerspectiveManager() table = Table(data) view = table.view() manager.host_table("table1", table) manager.host_view("view1", view) manager._process(message, post_callback)
def test_manager_on_update(self, sentinel): s = sentinel(0) def update_callback(port_id): s.set(s.get() + 1) # create a table and view using manager make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]} manager = PerspectiveManager() manager._process(make_table, self.post) make_view = {"id": 2, "table_name": "table1", "view_name": "view1", "cmd": "view"} manager._process(make_view, self.post) # hook into the created view and pass it the callback view = manager._views["view1"] view.on_update(update_callback) # call updates update1 = {"id": 3, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [4], "b": ["d"]}]} update2 = {"id": 4, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [5], "b": ["e"]}]} manager._process(update1, self.post) manager._process(update2, self.post) assert s.get() == 2
def make_app(): here = os.path.abspath(os.path.dirname(__file__)) DATA_HOST = DataHost() MANAGER = PerspectiveManager(lock=True) STATE_TABLE = DATA_HOST.state_table COUNTY_TABLE = DATA_HOST.county_table MANAGER.host_view("state_data_source", STATE_TABLE.view()) MANAGER.host_view("county_data_source", COUNTY_TABLE.view()) return tornado.web.Application([ (r"/", MainHandler), # create a websocket endpoint that the client Javascript can access (r"/static/(.*)", StaticHandler, {"path": os.path.join(here, "static")}), (r"/ws", PerspectiveTornadoHandler, {"manager": MANAGER, "check_origin": True}) ])
def test_manager_delete_view(self): make_table = { "id": 1, "name": "table1", "cmd": "table", "args": [data] } manager = PerspectiveManager() manager._process(make_table, self.post) make_view = { "id": 2, "table_name": "table1", "view_name": "view1", "cmd": "view" } manager._process(make_view, self.post) delete_view = { "id": 3, "name": "view1", "cmd": "view_method", "method": "delete" } manager._process(delete_view, self.post) assert len(manager._views) == 0
def test_manager_set_queue_process(self, sentinel): s = sentinel(0) manager = PerspectiveManager() table = Table({"a": [1, 2, 3]}) manager.host_table("tbl", table) table.update({"a": [4, 5, 6]}) assert table.view().to_dict() == { "a": [1, 2, 3, 4, 5, 6] } def fake_queue_process(table_id, state_manager): s.set(s.get() + 1) state_manager.call_process(table_id) manager._set_queue_process(fake_queue_process) table.update({"a": [7, 8, 9]}) assert s.get() == 1
def test_async_queue_process_csv(self): """Make sure GIL release during CSV loading works""" tbl = Table("x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false") manager = PerspectiveManager() manager.set_loop_callback(TestAsync.loop.add_callback) manager.host(tbl) @syncify def _task(): assert tbl.size() == 4 for i in range(5): tbl.update("x,y,z\n1,a,true\n2,b,false\n3,c,true\n4,d,false") return tbl.size() assert _task() == 24 tbl.delete()
def test_manager_clear_view(self): messages = [{ "id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view" }, { "id": 2, "table_name": "table1", "view_name": "view2", "cmd": "view" }, { "id": 3, "table_name": "table1", "view_name": "view3", "cmd": "view" }] manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) for message in messages: manager._process(message, self.post, client_id=1) manager.clear_views(1) assert manager._views == {}
def test_manager_table_get_computation_input_types(self): post_callback = partial(self.validate_post, expected={ "id": 1, "data": ["string"] }) message = { "id": 1, "name": "table1", "cmd": "table_method", "method": "get_computation_input_types", "args": ["concat_comma"] } manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) manager._process(message, post_callback)
def test_manager_set_queue_process(self, sentinel): s = sentinel(0) manager = PerspectiveManager() table = Table({"a": [1, 2, 3]}) manager.host_table("tbl", table) table.update({"a": [4, 5, 6]}) assert table.view().to_dict() == { "a": [1, 2, 3, 4, 5, 6] } def fake_queue_process(f, *args, **kwargs): s.set(s.get() + 1) f(*args, **kwargs) manager.set_loop_callback(fake_queue_process) table.update({"a": [7, 8, 9]}) assert s.get() == 2
def test_manager_clear_view_no_client_id(self): messages = [{ "id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view" }, { "id": 2, "table_name": "table1", "view_name": "view2", "cmd": "view" }, { "id": 3, "table_name": "table1", "view_name": "view3", "cmd": "view" }] manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) for message in messages: manager._process(message, self.post) with raises(PerspectiveError): manager.clear_views(None)
def test_manager_create_indexed_table(self): message = { "id": 1, "name": "table1", "cmd": "table", "args": [data], "options": { "index": "a" } } manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) manager._process(message, self.post) assert manager._tables["table1"].schema() == {"a": int, "b": str} assert manager._tables["table1"]._index == "a"
def make_app(): manager = PerspectiveManager() # Run Perspective in its own thread with its own IOLoop thread = threading.Thread(target=perspective_thread, args=(manager, )) thread.daemon = True thread.start() return tornado.web.Application( [ ( r"/websocket", PerspectiveTornadoHandler, { "manager": manager, "check_origin": True }, ), ( r"/node_modules/(.*)", tornado.web.StaticFileHandler, { "path": "../../../node_modules/@finos/" }, ), ( r"/(.*)", tornado.web.StaticFileHandler, { "path": os.path.join(here, "..", "dist"), "default_filename": "index.html", }, ), ], websocket_ping_interval=15, )
def test_manager_create_view_one(self): message = { "id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view", "config": { "row_pivots": ["a"] } } manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) manager._process(message, self.post) assert manager._views["view1"].to_dict() == { "__ROW_PATH__": [[], ["1"], ["2"], ["3"]], "a": [6, 1, 2, 3], "b": [3, 1, 1, 1] }
def test_async_queue_process(self): tbl = Table({"a": int, "b": float, "c": str}) manager = PerspectiveManager() manager._set_queue_process(TestAsync.wrapped_queue_process) manager.host(tbl) assert tbl.size() == 0 for i in range(5): tbl.update([data[i]]) table_id = tbl._table.get_id() pool = tbl._table.get_pool() assert _PerspectiveStateManager.TO_PROCESS == {table_id: pool} assert tbl.view().to_records() == data[:5] # should have flushed the process queue assert _PerspectiveStateManager.TO_PROCESS == {}
def test_async_queue_process(self): tbl = Table({ "a": int, "b": float, "c": str }) manager = PerspectiveManager() manager._set_queue_process(TestAsync.wrapped_queue_process) manager.host(tbl) assert tbl.size() == 0 for i in range(5): tbl.update([data[i]]) # process should have been called at least once assert SENTINEL.get() > 0 tbl.delete()
def test_async_queue_process_multiple_ports(self): tbl = Table({ "a": int, "b": float, "c": str }) port_ids = [0] port_data = [{ "a": 0, "b": 0, "c": "0" }] for i in range(10): port_id = tbl.make_port() port_ids.append(port_id) port_data.append({ "a": port_id, "b": port_id * 1.5, "c": str(port_id) }) assert port_ids == list(range(0, 11)) manager = PerspectiveManager() manager._set_queue_process(TestAsync.wrapped_queue_process) manager.host(tbl) assert tbl.size() == 0 random.shuffle(port_ids) for port_id in port_ids: idx = port_id if port_id < len(port_ids) else len(port_ids) - 1 tbl.update([port_data[idx]], port_id=port_id) # assert that process is being called asynchronously assert SENTINEL.get() > 0 tbl.delete()
def test_manager_table_validate_expressions(self): post_callback = partial(self.validate_post, expected={ "id": 1, "data": { "expression_schema": { "abc": "float" }, "errors": {} } }) message = { "id": 1, "name": "table1", "cmd": "table_method", "method": "validate_expressions", "args": [['// abc \n "a" + "a"']] } manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) manager._process(message, post_callback)
def test_session_new_session(self, sentinel): s = sentinel(False) def handle_to_dict(msg): s.set(True) message = json.loads(msg) assert message["data"] == data message = { "id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view" } manager = PerspectiveManager() session = manager.new_session() client_id = session.client_id table = Table(data) manager.host_table("table1", table) # create a view through the session to make sure it has a client id session.process(message, self.post) # make sure the client ID is attached to the new view assert len(manager._views.keys()) == 1 assert manager._get_view("view1")._client_id == client_id to_dict_message = { "id": 2, "name": "view1", "cmd": "view_method", "method": "to_dict" } session.process(to_dict_message, handle_to_dict) assert s.get() is True
def make_app(): # Create an instance of `PerspectiveManager` and a table. MANAGER = PerspectiveManager() TABLE = None TABLE = Table(pd.read_csv(os.path.join(here, "superstore.csv")), index="Row ID") # Track the table with the name "data_source_one", which will be used in # the front-end to access the Table. MANAGER.host_table("data_source_one", TABLE) MANAGER.host_view("view_one", TABLE.view()) return tornado.web.Application( [ (r"/", MainHandler), # create a websocket endpoint that the client Javascript can access (r"/websocket", PerspectiveTornadoHandler, { "manager": MANAGER, "check_origin": True }) ], websocket_ping_interval=15)
def test_manager_create_view_two(self): message = { "id": 1, "table_name": "table1", "view_name": "view1", "cmd": "view", "config": { "group_by": ["a"], "split_by": ["b"] } } manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) manager._process(message, self.post) assert manager._views["view1"].to_dict() == { "__ROW_PATH__": [[], [1], [2], [3]], "a|a": [1, 1, None, None], "a|b": [1, 1, None, None], "b|a": [2, None, 2, None], "b|b": [1, None, 1, None], "c|a": [3, None, None, 3], "c|b": [1, None, None, 1] }
def test_session_close_multiple_sessions_with_callbacks(self, sentinel): s = sentinel(0) manager = PerspectiveManager() sessions = [manager.new_session() for i in range(5)] # create a table and view using manager make_table = { "id": 1, "name": "table1", "cmd": "table", "args": [data] } manager._process(make_table, self.post) # create a view on each session for i, session in enumerate(sessions): # IDs have to conflict - each viewer will send the first message as # ID = 1, so we need to make sure we handle that. msg = { "id": 2, "table_name": "table1", "view_name": "view" + str(i), "cmd": "view" } session.process(msg, self.post) manager_views = list(manager._views.keys()) for key in ["view" + str(i) for i in range(5)]: assert key in manager_views for i, session in enumerate(sessions): view = manager._get_view("view" + str(i)) assert view._client_id == session.client_id def callback(updated): assert updated["port_id"] == 0 s.set(s.get() + 100) # simulate a client that holds callbacks by id callbacks = {3: callback} def post_update(msg): # when `on_update` is triggered, this callback gets the message # and has to decide which callback to trigger. message = json.loads(msg) assert message["id"] is not None if message["id"] == 3: # trigger callback assert message["data"] == {"port_id": 0} callbacks[message["id"]](message["data"]) # create a view and an on_update on each session for i, session in enumerate(sessions): view_name = "view" + str(i) # IDs have to conflict - each viewer will send the first message as # ID = 1, so we need to make sure we handle that. msg = { "id": 2, "table_name": "table1", "view_name": view_name, "cmd": "view" } session.process(msg, self.post) make_on_update = { "id": 3, "name": view_name, "cmd": "view_method", "subscribe": True, "method": "on_update", "callback_id": "callback_1" } session.process(make_on_update, post_update) # call updates using a random session - they should propagate random_session_id = random.randint(0, 4) random_session = sessions[random_session_id] random_client_id = random_session.client_id update1 = { "id": 4, "name": "table1", "cmd": "table_method", "method": "update", "args": [{ "a": [4], "b": ["d"] }] } update2 = { "id": 5, "name": "table1", "cmd": "table_method", "method": "update", "args": [{ "a": [5], "b": ["e"] }] } random_session.process(update1, self.post) random_session.process(update2, self.post) # all updates processed, all callbacks fired assert s.get() == 1000 # close a random session, and make sure the other views and callbacks # are not affected random_session.close() # make sure the view is gone - but not the table assert "table1" in manager._tables assert "view" + str(random_session_id) not in manager._views.keys() assert len(manager._views.keys()) == 4 for callback in manager._callback_cache: assert callback["client_id"] != random_client_id assert len(manager._callback_cache) == 4
def test_session_close_session_with_callbacks(self, sentinel): s = sentinel(0) manager = PerspectiveManager() session = manager.new_session() client_id = session.client_id # create a table and view using manager make_table = { "id": 1, "name": "table1", "cmd": "table", "args": [data] } session.process(make_table, self.post) make_view = { "id": 2, "table_name": "table1", "view_name": "view1", "cmd": "view" } session.process(make_view, self.post) # make sure the client ID is attached to the new view assert len(manager._views.keys()) == 1 assert manager._get_view("view1")._client_id == client_id def callback(updated): assert updated["port_id"] == 0 s.set(s.get() + 100) # simulate a client that holds callbacks by id callbacks = {3: callback} def post_update(msg): # when `on_update` is triggered, this callback gets the message # and has to decide which callback to trigger. message = json.loads(msg) assert message["id"] is not None if message["id"] == 3: # trigger callback assert message["data"] == {"port_id": 0} callbacks[message["id"]](message["data"]) # hook into the created view and pass it the callback make_on_update = { "id": 3, "name": "view1", "cmd": "view_method", "subscribe": True, "method": "on_update", "callback_id": "callback_1" } session.process(make_on_update, post_update) # call updates update1 = { "id": 4, "name": "table1", "cmd": "table_method", "method": "update", "args": [{ "a": [4], "b": ["d"] }] } update2 = { "id": 5, "name": "table1", "cmd": "table_method", "method": "update", "args": [{ "a": [5], "b": ["e"] }] } session.process(update1, self.post) session.process(update2, self.post) assert s.get() == 200 # close the session session.close() # make sure the view is gone - but not the table assert "table1" in manager._tables assert manager._views == {} assert len(manager._callback_cache) == 0
def test_manager_remove_update_through_wire_API(self, sentinel): s = sentinel(0) def update_callback(port_id, delta): s.set(s.get() + 1) # create a table and view using manager make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]} manager = PerspectiveManager() manager._process(make_table, self.post) make_view = {"id": 2, "table_name": "table1", "view_name": "view1", "cmd": "view"} manager._process(make_view, self.post) def callback(updated): assert updated["port_id"] == 0 s.set(s.get() + 100) # simulate a client that holds callbacks by id callbacks = { 3: callback } def post_update(msg): # when `on_update` is triggered, this callback gets the message # and has to decide which callback to trigger. message = json.loads(msg) assert message["id"] is not None if message["id"] == 3: # trigger callback assert message["data"] == { "port_id": 0 } callbacks[message["id"]](message["data"]) # create an on_update callback make_on_update = {"id": 3, "name": "view1", "cmd": "view_method", "subscribe": True, "method": "on_update", "callback_id": "callback_1"} manager._process(make_on_update, post_update) # call updates update1 = {"id": 4, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [4], "b": ["d"]}]} manager._process(update1, self.post) # remove update callback remove_on_update = {"id": 5, "name": "view1", "cmd": "view_method", "subscribe": True, "method": "remove_update", "callback_id": "callback_1"} manager._process(remove_on_update, self.post) update2 = {"id": 6, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [5], "b": ["e"]}]} manager._process(update2, self.post) assert s.get() == 100
def test_manager_on_update_rows_with_port_id_through_wire_API(self, sentinel): s = sentinel(0) def update_callback(port_id, delta): table = Table(delta) assert table.size() == 1 assert table.schema() == { "a": int, "b": str } table.delete() s.set(s.get() + 1) # create a table and view using manager make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]} manager = PerspectiveManager() manager._process(make_table, self.post) make_view = {"id": 2, "table_name": "table1", "view_name": "view1", "cmd": "view"} manager._process(make_view, self.post) # Get two ports on the table make_port = {"id": 3, "name": "table1", "cmd": "table_method", "method": "make_port"} make_port2 = {"id": 4, "name": "table1", "cmd": "table_method", "method": "make_port"} manager._process(make_port, self.post) manager._process(make_port2, self.post) # define an update callback def callback(delta): table = Table(delta) assert table.size() == 1 assert table.schema() == { "a": int, "b": str } table.delete() s.set(s.get() + 100) # simulate a client that holds callbacks by id callbacks = { 3: callback } def post_update(msg, binary=False): # when `on_update` is triggered, this callback gets the message # and has to decide which callback to trigger. if binary: # trigger callback - "msg" here is binary callbacks[3](msg) return message = json.loads(msg) assert message["id"] is not None if message["id"] == 3: # wait for transferable assert message["data"]["port_id"] in (1, 2) return # hook into the created view and pass it the callback make_on_update = { "id": 5, "name": "view1", "cmd": "view_method", "subscribe": True, "method": "on_update", "callback_id": "callback_1", "args": [{"mode": "row"}] } manager._process(make_on_update, post_update) # call updates update1 = {"id": 6, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [4], "b": ["d"]}, {"port_id": 1}]} update2 = {"id": 7, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [5], "b": ["e"]}, {"port_id": 2}]} manager._process(update1, self.post) manager._process(update2, self.post) assert s.get() == 200
def test_manager_on_update_rows_with_port_id(self, sentinel): s = sentinel(0) def update_callback(port_id, delta): table = Table(delta) assert table.size() == 1 assert table.schema() == { "a": int, "b": str } table.delete() s.set(s.get() + 1) # create a table and view using manager make_table = {"id": 1, "name": "table1", "cmd": "table", "args": [data]} manager = PerspectiveManager() manager._process(make_table, self.post) make_view = {"id": 2, "table_name": "table1", "view_name": "view1", "cmd": "view"} manager._process(make_view, self.post) # Get two ports on the table make_port = {"id": 3, "name": "table1", "cmd": "table_method", "method": "make_port"} make_port2 = {"id": 4, "name": "table1", "cmd": "table_method", "method": "make_port"} manager._process(make_port, self.post) manager._process(make_port2, self.post) # hook into the created view and pass it the callback view = manager._views["view1"] view.on_update(update_callback, mode="row") # call updates update1 = {"id": 5, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [4], "b": ["d"]}, {"port_id": 1}]} update2 = {"id": 6, "name": "table1", "cmd": "table_method", "method": "update", "args": [{"a": [5], "b": ["e"]}, {"port_id": 2}]} manager._process(update1, self.post) manager._process(update2, self.post) assert s.get() == 2
def test_manager_host_table_transitive(self): manager = PerspectiveManager() table = Table(data) manager.host_table("table1", table) table.update({"a": [4, 5, 6], "b": ["d", "e", "f"]}) assert manager.get_table("table1").size() == 6
def test_manager_host_invalid(self): manager = PerspectiveManager() with raises(PerspectiveError): manager.host({})
def test_arbitary_lock_unlock_manager(self): manager = PerspectiveManager(lock=True) make_table_message = {"id": 1, "name": "table1", "cmd": "table", "args": [data]} manager._process(make_table_message, partial(self.validate_post, expected={ "id": 1, "error": "`table` failed - access denied" })) manager.unlock() manager._process(make_table_message, self.post) assert manager._tables["table1"].schema() == { "a": int, "b": str } update_message = {"id": 2, "name": "table1", "cmd": "table_method", "method": "update", "args": [data]} manager._process(update_message, self.post) assert manager._tables["table1"].size() == 6 manager.lock() update_message_new = {"id": 3, "name": "table1", "cmd": "table_method", "method": "update", "args": [data]} manager._process(update_message_new, partial(self.validate_post, expected={ "id": 3, "error": "`table_method.update` failed - access denied" }))
def test_session_multiple_new_sessions(self, sentinel): s = sentinel(0) def handle_to_dict(msg): s.set(s.get() + 1) message = json.loads(msg) assert message["data"] == { "a": [1, 2, 3, 1, 2, 3], "b": ["a", "b", "c", "str1", "str2", "str3"] } manager = PerspectiveManager() sessions = [manager.new_session() for i in range(5)] table = Table(data) manager.host_table("table1", table) # create a view on each session for i, session in enumerate(sessions): # IDs have to conflict - each viewer will send the first message as # ID = 1, so we need to make sure we handle that. msg = { "id": 1, "table_name": "table1", "view_name": "view" + str(i), "cmd": "view" } session.process(msg, self.post) manager_views = list(manager._views.keys()) for key in ["view" + str(i) for i in range(5)]: assert key in manager_views for i, session in enumerate(sessions): view = manager._get_view("view" + str(i)) assert view._client_id == session.client_id # arbitrarily do an update random_session_id = random.randint(0, 4) update_message = { "id": 2, "name": "table1", "cmd": "table_method", "method": "update", "args": [{ "a": [1, 2, 3], "b": ["str1", "str2", "str3"] }] } sessions[random_session_id].process(update_message, self.post) # should reflect in all sessions for i, session in enumerate(sessions): to_dict_message = { "id": 3, "name": "view" + str(i), "cmd": "view_method", "method": "to_dict" } session.process(to_dict_message, handle_to_dict) assert s.get() == 5
def start(self): if self.debug: logging.getLogger().setLevel(logging.DEBUG) self.port = int(os.environ.get('PORT', self.port)) self.wspath = self.wspath.format(self.port) self._stash = [] engine = create_engine(self.sql_url, echo=False) Base.metadata.create_all(engine) self.sessionmaker = sessionmaker(bind=engine) session = self.sessionmaker() clients = session.query(Client).all() self._clients = {c.client_id: c for c in clients} self._manager = PerspectiveManager() self._competitions = Table( list(c.to_dict() for c in session.query(Competition).all())) self._submissions = Table( list(s.to_dict() for s in session.query(Submission).all())) self._leaderboards = Table({"a": int}) self._stash = [] self._table = Table([{"a": 1, "b": 2}]) self._manager.host_table("data_source_one", self._table) self._manager.host_table("competitions", self._competitions) self._manager.host_table("submissions", self._submissions) self._manager.host_table("leaderboards", self._leaderboards) root = os.path.join(os.path.dirname(__file__), 'assets') static = os.path.join(root, 'static') context = { 'sessionmaker': self.sessionmaker, 'clients': self._clients, 'competitions': self._competitions, 'submissions': self._submissions, 'leaderboards': self._leaderboards, 'stash': self._stash, 'basepath': self.basepath, 'wspath': self.wspath, 'proxies': 'test' } default_handlers = [ (r"/", HTMLOpenHandler, { 'template': 'index.html', 'context': context }), (r"/index.html", HTMLOpenHandler, { 'template': 'index.html', 'context': context, 'template_kwargs': {} }), (r"/home", HTMLOpenHandler, { 'template': 'home.html', 'context': context }), (r"/login", HTMLOpenHandler, { 'template': 'login.html', 'context': context }), (r"/register", HTMLOpenHandler, { 'template': 'login.html', 'context': context }), (r"/logout", HTMLOpenHandler, { 'template': 'logout.html', 'context': context }), ] default_handlers.extend([ (r"/api/v1/login", LoginHandler, context), (r"/api/v1/logout", LogoutHandler, context), (r"/api/v1/register", RegisterHandler, context), (r"/api/v1/apikeys", APIKeyHandler, context), (r"/api/v1/competition", CompetitionHandler, context), (r"/api/v1/wscompetition", PerspectiveTornadoHandler, { "manager": self._manager, "check_origin": True }), (r"/api/v1/submission", SubmissionHandler, context), (r"/api/v1/leaderboard", LeaderboardHandler, context), (r"/static/(.*)", tornado.web.StaticFileHandler, { "path": static }), (r"/(.*)", HTMLOpenHandler, { 'template': '404.html', 'context': context }) ]) for handler in self.handlers: for i, default in enumerate(default_handlers): if default[0] == handler[0]: # override default handler d = default[2] d.update(handler[2]) default_handlers[i] = (handler[0], handler[1], d) settings = { "cookie_secret": self.cookie_secret, "login_url": self.basepath + "login", "debug": self.debug, "template_path": os.path.join(root, 'templates'), } application = tornado.web.Application(default_handlers, **settings) logging.critical('LISTENING: %d', self.port) application.listen(self.port) tornado.ioloop.IOLoop.current().start()