├── .gitignore ├── README.md ├── __init__.py ├── cache.py ├── config.py ├── form.py ├── mopee.py ├── plugins.py ├── requirements.txt ├── session.py ├── utils.py └── web.py /.gitignore: -------------------------------------------------------------------------------- 1 | *.pyc 2 | 3 | .ENV 4 | 5 | *.swp -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | xcat 2 | ==== 3 | 4 | 基于 tornado, Jinja2, Momoko 的异步 web 框架 5 | -------------------------------------------------------------------------------- /__init__.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2013-05-27 16:47:02 4 | # @Author : vfasky (vfasky@gmail.com) 5 | # @Link : http://vfasky.com 6 | # @Version : $Id$ 7 | -------------------------------------------------------------------------------- /cache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date: 2013-05-10 09:55:54 4 | # @Author: vfasky (vfasky@gmail.com) 5 | # @Version: $Id$ 6 | 7 | ''' 8 | 持久化缓存 9 | ''' 10 | import time 11 | import asyncmongo 12 | from tornado import gen 13 | 14 | __all__ = ['Mongod'] 15 | 16 | ''' 17 | 基于 asyncmongo 的异步缓存 18 | ============================ 19 | 20 | ## demo: 21 | 22 | ``` python 23 | 24 | mongod = Mongod() 25 | 26 | class Handler(RequestHandler): 27 | @asynchronous 28 | @gen.engine 29 | def get(self): 30 | # 写缓存, 缓存有效期,1小时 31 | ret = yield gen.Task(mongod.set, 'test2', {'hello': 'word'}, 3600) 32 | print ret 33 | # 读缓存 34 | data = yield gen.Task(mongod.get, 'test2') 35 | print data 36 | # 删缓存 37 | ret = yield gen.Task(mongod.remove, 'test2') 38 | print ret 39 | 40 | ``` 41 | 42 | ''' 43 | 44 | 45 | class Mongod(object): 46 | 47 | def __init__(self, **kwargs): 48 | self._conn = asyncmongo.Client( 49 | pool_id = kwargs.get('pool_id', 'xcat.cache.Mongod'), 50 | host = kwargs.get('host', '127.0.0.1'), 51 | port = kwargs.get('port', 27017), 52 | maxcached = kwargs.get('maxcached', 10), 53 | maxconnections = kwargs.get('maxconnections', 50), 54 | dbname = kwargs.get('dbname', 'cache'), 55 | dbuser = kwargs.get('dbuser', None), 56 | dbpass = kwargs.get('dbpass', None) 57 | ) 58 | 59 | self._table = kwargs.get('table', 'caches') 60 | 61 | def get(self, key, default=None, callback=None): 62 | def _callback(data, error): 63 | if error: 64 | raise Error(error) 65 | if data: 66 | last_time = int(data['update_time']) + int(data['left_time']) 67 | 68 | if int(data['left_time']) == -1 or int(time.time()) <= last_time: 69 | return callback(data['val']) 70 | else: 71 | self.remove(key) 72 | 73 | callback(default) 74 | 75 | self._conn[self._table].find_one({'key': key}, callback=_callback) 76 | 77 | @gen.engine 78 | def set(self, key, val, left_time=-1, callback=None): 79 | def _callback(data, error): 80 | if error: 81 | raise Error(error) 82 | 83 | if callback: 84 | callback(len(data) == 1) 85 | 86 | ret, error = yield gen.Task(self._conn[self._table].find_one, {'key': key}) 87 | data = ret[0] 88 | if not data or len(data) == 0: 89 | self._conn[self._table].insert({ 90 | 'key' : key, 91 | 'val' : val, 92 | 'left_time' : int(left_time), 93 | 'update_time' : int(time.time()), 94 | }, callback=_callback) 95 | else: 96 | self._conn[self._table].update({ 97 | '_id' : data['_id'] 98 | },{ 99 | 'key' : key, 100 | 'val' : val, 101 | 'left_time' : int(left_time), 102 | 'update_time' : int(time.time()), 103 | }, upsert=True, safe=True, callback=_callback) 104 | 105 | def remove(self, key, callback=None): 106 | def _callback(data, error): 107 | if error: 108 | raise Error(error) 109 | 110 | if callback: 111 | callback(len(data) == 1) 112 | 113 | self._conn[self._table].remove({ 114 | 'key' : key 115 | }, callback=_callback) 116 | 117 | # 测试 118 | if __name__ == '__main__': 119 | from tornado.ioloop import IOLoop 120 | from tornado.httpserver import HTTPServer 121 | #from tornado.options import parse_command_line 122 | from tornado.web import asynchronous, RequestHandler, Application 123 | 124 | mongod = Mongod() 125 | 126 | class Handler(RequestHandler): 127 | @asynchronous 128 | @gen.engine 129 | def get(self): 130 | ret = yield gen.Task(mongod.set, 'test2', {'hello': 'word'}) 131 | print ret 132 | data = yield gen.Task(mongod.get, 'test2') 133 | print data 134 | ret = yield gen.Task(mongod.remove, 'test2') 135 | print ret 136 | 137 | self.finish() 138 | 139 | application = Application([ 140 | (r'/', Handler), 141 | ], debug=True) 142 | 143 | http_server = HTTPServer(application) 144 | http_server.listen(8181) 145 | IOLoop.instance().start() 146 | -------------------------------------------------------------------------------- /config.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2013-08-23 16:47:02 4 | # @Author : vfasky (vfasky@gmail.com) 5 | # @Link : http://vfasky.com 6 | # @Version : $Id$ 7 | 8 | ''' 9 | 框架全局配置文件的读写 10 | ''' 11 | __all__ = [ 12 | 'load', 13 | 'set', 14 | 'get', 15 | ] 16 | 17 | import copy 18 | 19 | _config = { 20 | 'run_mode': 'devel', 21 | 'acls': [], 22 | 'login_url': '/login', 23 | 'version': '1.0.0', 24 | 'app_path': '', 25 | 'root_path': '', 26 | 'static_path': '', 27 | 'template_path': '', 28 | 'locale_path': '', 29 | 'debug': True, 30 | 'gzip': True, 31 | 'cookie_secret': 'this-Xcat-app', 32 | 'xsrf_cookies': True, 33 | 'autoescape': None, 34 | 'sync_key': 'xcat.web.Application.id', 35 | 'devel': { 36 | 'database': None, 37 | 'session': None, 38 | 'cache': None, 39 | }, 40 | 'deploy': { 41 | 'database': None, 42 | 'session': None, 43 | 'cache': None, 44 | }, 45 | } 46 | # 运行模式类型 47 | _model_type = ('devel', 'deploy') 48 | # 当前运行模式 49 | _run_mode = 'devel' 50 | 51 | # 设置配置 52 | def load(config): 53 | global _config, _run_mode 54 | 55 | if config.has_key('run_mode')\ 56 | and config['run_mode'] in _model_type\ 57 | and config.has_key(config['run_mode']): 58 | _run_mode = config['run_mode'] 59 | _evn_cfg = copy.copy(_config[_run_mode]) 60 | _evn_cfg.update(config[_run_mode]) 61 | 62 | _config.update(config) 63 | _config.update(_evn_cfg) 64 | else: 65 | raise NameError, 'config syntax' 66 | 67 | # 设置配置 68 | def set(key, value): 69 | global _config 70 | _config[key] = value 71 | 72 | # 设置配置 73 | def get(key=None, default=None): 74 | if None == key: 75 | return _config 76 | elif _config.has_key(key): 77 | return _config[key] 78 | elif _config[_run_mode].has_key(key): 79 | return _config[_run_mode].has_key(key) 80 | return default 81 | 82 | # 测试 83 | if __name__ == '__main__': 84 | set({ 85 | 'static_path' : 'test', 86 | 'run_mode': 'devel', 87 | 'devel': { 88 | 'test' : 'ok?' 89 | } 90 | }) 91 | print get() 92 | print '======' 93 | print get('test') -------------------------------------------------------------------------------- /form.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2013-05-29 15:08:04 4 | # @Author : vfasky (vfasky@gmail.com) 5 | # @Link : http://vfasky.com 6 | # @Version : $Id$ 7 | 8 | __all__ = [ 9 | 'Form', 10 | 'fields', 11 | 'validators', 12 | 'widgets', 13 | 'ValidationError' 14 | ] 15 | import re 16 | import types 17 | import tornado.locale 18 | from tornado.escape import to_unicode 19 | from wtforms import Form as wtForm, fields, validators, widgets, ValidationError 20 | from wtforms.compat import iteritems 21 | 22 | class Form(wtForm): 23 | """ 24 | Using this Form instead of wtforms.Form 25 | 26 | Example:: 27 | 28 | class SigninForm(Form): 29 | email = EmailField('email') 30 | password = PasswordField('password') 31 | 32 | class SigninHandler(RequestHandler): 33 | def get(self): 34 | form = SigninForm(self.request.arguments, locale_code=self.locale.code) 35 | 36 | """ 37 | def __init__(self, formdata=None, obj=None, prefix='', locale_code='en_US', **kwargs): 38 | self._locale_code = locale_code 39 | super(Form, self).__init__(formdata, obj, prefix, **kwargs) 40 | 41 | def process(self, formdata=None, obj=None, **kwargs): 42 | if formdata is not None and not hasattr(formdata, 'getlist'): 43 | formdata = TornadoArgumentsWrapper(formdata) 44 | super(Form, self).process(formdata, obj, **kwargs) 45 | 46 | 47 | def load_data(self, obj): 48 | formdata = TornadoArgumentsWrapper(MopeeObjWrapper(obj, self)) 49 | return self.process(formdata) 50 | 51 | 52 | def _get_translations(self): 53 | if not hasattr(self, '_locale_code'): 54 | self._locale_code = 'en_US' 55 | return TornadoLocaleWrapper(self._locale_code) 56 | 57 | def MopeeObjWrapper(obj, form): 58 | data = {} 59 | model = obj 60 | if type(obj) is types.DictType: 61 | for field in form._fields: 62 | if model.has_key(field): 63 | value = model.get(field) 64 | if type(value) is types.ListType: 65 | data[field] = value 66 | else: 67 | data[field] = [ str(value) ] 68 | else: 69 | for field in form._fields: 70 | if hasattr(model, field): 71 | value = getattr(model,field) 72 | if type(value) is types.ListType: 73 | data[field] = value 74 | else: 75 | data[field] = [ str(value) ] 76 | return data 77 | 78 | 79 | class TornadoArgumentsWrapper(dict): 80 | def __getattr__(self, key): 81 | try: 82 | return self[key] 83 | except KeyError: 84 | raise AttributeError 85 | 86 | def __setattr__(self, key, value): 87 | self[key] = value 88 | 89 | def __delattr__(self, key): 90 | try: 91 | del self[key] 92 | except KeyError: 93 | raise AttributeError 94 | 95 | def getlist(self, key): 96 | try: 97 | values = [] 98 | for v in self[key]: 99 | if type(v) is types.StringType: 100 | v = to_unicode(v) 101 | if isinstance(v, unicode): 102 | v = re.sub(r"[\x00-\x08\x0e-\x1f]", " ", v) 103 | values.append(v) 104 | return values 105 | except KeyError: 106 | raise AttributeError 107 | 108 | 109 | class TornadoLocaleWrapper(object): 110 | def __init__(self, code): 111 | self.locale = tornado.locale.get(code) 112 | 113 | def gettext(self, message): 114 | return self.locale.translate(message) 115 | 116 | def ngettext(self, message, plural_message, count): 117 | return self.locale.translate(message, plural_message, count) 118 | -------------------------------------------------------------------------------- /mopee.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date: 2013-05-02 17:13:37 4 | # @Author: vfasky (vfasky@gmail.com) 5 | # @Version: $Id$ 6 | # peewee for Momoko 7 | 8 | __all__ = [ 9 | 'BigIntegerField', 10 | 'BlobField', 11 | 'BooleanField', 12 | 'CharField', 13 | 'DateField', 14 | 'DateTimeField', 15 | 'DecimalField', 16 | 'DoubleField', 17 | 'Field', 18 | 'FloatField', 19 | 'fn', 20 | 'ForeignKeyField', 21 | 'IntegerField', 22 | 'TextField', 23 | 'TimeField', 24 | 'PostgresqlAsyncDatabase', 25 | 'WaitAllOps', 26 | 'AsyncModel', 27 | ] 28 | 29 | ''' 30 | Momoko ORM(peewee) 31 | ================== 32 | 33 | ## demo 34 | 35 | ``` python 36 | import mopee 37 | 38 | from tornado import gen 39 | from tornado.ioloop import IOLoop 40 | from tornado.httpserver import HTTPServer 41 | from tornado.web import asynchronous, RequestHandler, Application 42 | 43 | database = mopee.PostgresqlAsyncDatabase('test', 44 | user = 'vfasky', 45 | password = '', 46 | size = 20, 47 | ) 48 | 49 | database.connect() 50 | 51 | class User(mopee.AsyncModel): 52 | class Meta: 53 | database = database 54 | 55 | name = mopee.CharField() 56 | password = mopee.CharField(max_length = 255) 57 | 58 | class AsyncHandler(RequestHandler): 59 | @asynchronous 60 | @gen.engine 61 | def get(self): 62 | # 判断表是否存在 63 | exists = yield gen.Task(User.table_exists) 64 | # 如果不存在,创建表 65 | if not exists: 66 | User.create_table() 67 | 68 | # 添加数据 69 | user = User( 70 | name = 'vfasky', 71 | password = '1233', 72 | ) 73 | pk = yield gen.Task(user.save) 74 | 75 | # 查询表 76 | user = yield gen.Task(User.select().where(User.id == pk).get) 77 | self.write(user.name) 78 | 79 | self.finish() 80 | 81 | if __name__ == '__main__': 82 | 83 | application = Application([ 84 | (r'/', AsyncHandler), 85 | ], debug=True) 86 | 87 | http_server = HTTPServer(application) 88 | http_server.listen(8181, 'localhost') 89 | IOLoop.instance().start() 90 | ``` 91 | 92 | ''' 93 | 94 | from tornado import gen 95 | from peewee import PostgresqlDatabase, Query, \ 96 | SelectQuery, UpdateQuery, InsertQuery, \ 97 | DeleteQuery, Model, QueryResultWrapper, \ 98 | RawQuery, DictQueryResultWrapper, \ 99 | NaiveQueryResultWrapper, \ 100 | ModelQueryResultWrapper, \ 101 | ModelAlias, with_metaclass, \ 102 | BaseModel, CharField, DateTimeField, \ 103 | DateField, TimeField, DecimalField, \ 104 | ForeignKeyField, PrimaryKeyField, \ 105 | TextField, IntegerField, BooleanField, \ 106 | FloatField, DoubleField, BigIntegerField, \ 107 | DecimalField, BlobField, fn, Field 108 | import momoko 109 | #import logging 110 | #logger = logging.getLogger('mopee') 111 | 112 | WaitAllOps = momoko.WaitAllOps 113 | 114 | class PostgresqlAsyncDatabase(PostgresqlDatabase): 115 | 116 | def _connect(self, database, **kwargs): 117 | return momoko.Pool( 118 | dsn='dbname=%s user=%s password=%s host=%s port=%s' % ( 119 | database, 120 | kwargs.get('user'), 121 | kwargs.get('password'), 122 | kwargs.get('host', 'localhost'), 123 | kwargs.get('port', '5432'), 124 | ), 125 | size=kwargs.get('size', 10) 126 | ) 127 | 128 | @gen.engine 129 | def last_insert_id(self, cursor, model, callback=None): 130 | seq = model._meta.primary_key.sequence 131 | if seq: 132 | sql = "SELECT CURRVAL('\"%s\"')" % (seq) 133 | cursor = yield momoko.Op(self.get_conn().execute, sql) 134 | callback(cursor.fetchone()[0]) 135 | return 136 | elif model._meta.auto_increment: 137 | sql = "SELECT CURRVAL('\"%s_%s_seq\"')" % ( 138 | model._meta.db_table, model._meta.primary_key.db_column) 139 | cursor = yield momoko.Op(self.get_conn().execute, sql) 140 | callback(cursor.fetchone()[0]) 141 | return 142 | 143 | callback(None) 144 | return 145 | 146 | def create_table(self, model_class, safe=False, callback=None): 147 | qc = self.compiler() 148 | return self.execute_sql(qc.create_table(model_class, safe), callback=callback) 149 | 150 | def create_index(self, model_class, fields, unique=False, callback=None): 151 | qc = self.compiler() 152 | if not isinstance(fields, (list, tuple)): 153 | raise ValueError('fields passed to "create_index" must be a list or tuple: "%s"' % fields) 154 | field_objs = [model_class._meta.fields[f] if isinstance(f, basestring) else f for f in fields] 155 | return self.execute_sql(qc.create_index(model_class, field_objs, unique), callback=callback) 156 | 157 | def create_foreign_key(self, model_class, field, callback=None): 158 | if not field.primary_key: 159 | return self.create_index(model_class, [field], field.unique, callback=callback) 160 | 161 | def create_sequence(self, seq, callback=None): 162 | if self.sequences: 163 | qc = self.compiler() 164 | return self.execute_sql(qc.create_sequence(seq), callback=callback) 165 | 166 | def drop_table(self, model_class, fail_silently=False, callback=None): 167 | qc = self.compiler() 168 | return self.execute_sql(qc.drop_table(model_class, fail_silently), callback=callback) 169 | 170 | def drop_sequence(self, seq, callback=None): 171 | if self.sequences: 172 | qc = self.compiler() 173 | return self.execute_sql(qc.drop_sequence(seq), callback=callback) 174 | 175 | 176 | 177 | def rows_affected(self, cursor): 178 | return cursor.rowcount 179 | 180 | 181 | def get_indexes_for_table(self, table, callback=None): 182 | def _callback(res): 183 | callback(sorted([(r[0], r[1]) for r in res.fetchall()])) 184 | 185 | self.execute_sql(""" 186 | SELECT c2.relname, i.indisprimary, i.indisunique 187 | FROM pg_catalog.pg_class c, pg_catalog.pg_class c2, pg_catalog.pg_index i 188 | WHERE c.relname = %s AND c.oid = i.indrelid AND i.indexrelid = c2.oid 189 | ORDER BY i.indisprimary DESC, i.indisunique DESC, c2.relname""", (table,), callback=_callback) 190 | 191 | @gen.engine 192 | def execute_sql(self, sql, params=None, require_commit=True, callback=None): 193 | params = params or () 194 | if require_commit and self.get_autocommit(): 195 | cursors = yield momoko.Op(self.get_conn().transaction, [(sql, params)]) 196 | 197 | for i, cursor in enumerate(cursors): 198 | pass 199 | else: 200 | cursor = yield momoko.Op(self.get_conn().execute, sql, params ) 201 | 202 | if callback and cursor: 203 | #print cursor 204 | callback(cursor) 205 | 206 | def get_tables(self, callback=None): 207 | def _callback(res): 208 | if callback: 209 | callback([row[0] for row in res.fetchall()]) 210 | 211 | self.execute_sql(""" 212 | SELECT c.relname 213 | FROM pg_catalog.pg_class c 214 | LEFT JOIN pg_catalog.pg_namespace n ON n.oid = c.relnamespace 215 | WHERE c.relkind IN ('r', 'v', '') 216 | AND n.nspname NOT IN ('pg_catalog', 'pg_toast') 217 | AND pg_catalog.pg_table_is_visible(c.oid) 218 | ORDER BY c.relname""", callback=_callback) 219 | 220 | def sequence_exists(self, sequence, callback=None): 221 | def _callback(res): 222 | callback(bool(res.fetchone()[0])) 223 | 224 | self.execute_sql(""" 225 | SELECT COUNT(*) 226 | FROM pg_class, pg_namespace 227 | WHERE relkind='S' 228 | AND pg_class.relnamespace = pg_namespace.oid 229 | AND relname=%s""", (sequence,), callback=_callback) 230 | 231 | def set_search_path(self, *search_path): 232 | path_params = ','.join(['%s'] * len(search_path)) 233 | self.execute_sql('SET search_path TO %s' % path_params, search_path) 234 | 235 | class AsyncQuery(Query): 236 | def _execute(self, callback=None): 237 | sql, params = self.sql() 238 | return self.database.execute_sql(sql, params, self.require_commit, callback=callback) 239 | 240 | class AsyncUpdateQuery(UpdateQuery): 241 | def _execute(self, callback=None): 242 | sql, params = self.sql() 243 | return self.database.execute_sql(sql, params, self.require_commit, callback=callback) 244 | 245 | def execute(self, callback=None): 246 | def _callback(cursor): 247 | ret = self.database.rows_affected(cursor) 248 | callback(ret) 249 | self._execute(callback=_callback) 250 | 251 | class AsyncDeleteQuery(DeleteQuery): 252 | def _execute(self, callback=None): 253 | sql, params = self.sql() 254 | return self.database.execute_sql(sql, params, self.require_commit, callback=callback) 255 | 256 | def execute(self, callback=None): 257 | def _callback(cursor): 258 | callback(self.database.rows_affected(cursor)) 259 | self._execute(callback=_callback) 260 | 261 | class AsyncInsertQuery(InsertQuery): 262 | def _execute(self, callback=None): 263 | sql, params = self.sql() 264 | return self.database.execute_sql(sql, params, self.require_commit, callback=callback) 265 | 266 | def execute(self, callback=None): 267 | def _callback(cursor): 268 | self.database.last_insert_id(cursor, self.model_class, callback) 269 | self._execute(callback=_callback) 270 | 271 | 272 | class AsyncRawQuery(RawQuery): 273 | def _execute(self, callback=None): 274 | sql, params = self.sql() 275 | return self.database.execute_sql(sql, params, self.require_commit, callback=callback) 276 | 277 | def execute(self, callback=None): 278 | if self._qr is None: 279 | if self._tuples: 280 | ResultWrapper = QueryResultWrapper 281 | elif self._dicts: 282 | ResultWrapper = DictQueryResultWrapper 283 | else: 284 | ResultWrapper = NaiveQueryResultWrapper 285 | 286 | def _callback(cursor): 287 | self._qr = ResultWrapper(self.model_class, cursor, None) 288 | callback(self._qr) 289 | 290 | self._execute(callback=_callback) 291 | else: 292 | callback(self._qr) 293 | 294 | def scalar(self, as_tuple=False, callback=None): 295 | def _callback(cursor): 296 | row = cursor.fetchone() 297 | if row and not as_tuple: 298 | row = row[0] 299 | callback(row) 300 | self._execute(callback=_callback) 301 | 302 | 303 | class AsyncSelectQuery(SelectQuery): 304 | def _execute(self, callback=None): 305 | sql, params = self.sql() 306 | self.database.execute_sql(sql, params, self.require_commit, callback=callback) 307 | 308 | def scalar(self, as_tuple=False, callback=None): 309 | def _callback(cursor): 310 | row = cursor.fetchone() 311 | if row and not as_tuple: 312 | row = row[0] 313 | callback(row) 314 | self._execute(callback=_callback) 315 | 316 | def execute(self, callback=None): 317 | if self._dirty or not self._qr: 318 | query_meta = None 319 | if self._tuples: 320 | ResultWrapper = QueryResultWrapper 321 | elif self._dicts: 322 | ResultWrapper = DictQueryResultWrapper 323 | elif self._naive or not self._joins or self.verify_naive(): 324 | ResultWrapper = NaiveQueryResultWrapper 325 | else: 326 | query_meta = [self._select, self._joins] 327 | ResultWrapper = ModelQueryResultWrapper 328 | 329 | def _callback(cursor): 330 | self._qr = ResultWrapper(self.model_class, cursor, query_meta) 331 | self._dirty = False 332 | callback(self._qr) 333 | 334 | self._execute(callback=_callback) 335 | else: 336 | callback(self._qr) 337 | 338 | def wrapped_count(self, callback=None): 339 | clone = self.order_by() 340 | clone._limit = clone._offset = None 341 | 342 | sql, params = clone.sql() 343 | wrapped = 'SELECT COUNT(1) FROM (%s) AS wrapped_select' % sql 344 | rq = AsyncRawQuery(self.model_class, wrapped, *params) 345 | 346 | def _callback(row): 347 | callback(row or 0) 348 | 349 | rq.scalar(callback=_callback) 350 | 351 | def aggregate(self, aggregation=None, callback=None): 352 | return self._aggregate(aggregation).scalar(callback=callback) 353 | 354 | def count(self, callback=None): 355 | def _callback(row): 356 | callback(row or 0) 357 | 358 | if self._distinct or self._group_by: 359 | return self.wrapped_count(callback=_callback) 360 | 361 | # defaults to a count() of the primary key 362 | return self.aggregate(callback=_callback) 363 | 364 | def exists(self, callback=None): 365 | clone = self.paginate(1, 1) 366 | clone._select = [self.model_class._meta.primary_key] 367 | def _callback(row): 368 | callback(bool(row)) 369 | 370 | clone.scalar(callback=_callback) 371 | 372 | @gen.engine 373 | def first(self, callback=None): 374 | res = yield gen.Task(self.execut) 375 | res.fill_cache(1) 376 | try: 377 | if callback: 378 | callback(res._result_cache[0]) 379 | return 380 | except IndexError: 381 | pass 382 | if callback: 383 | callback(None) 384 | 385 | 386 | @gen.engine 387 | def get(self, callback=None): 388 | clone = self.paginate(1, 1) 389 | 390 | try: 391 | cursor = yield gen.Task(clone.execute) 392 | if callback: 393 | callback(cursor.next()) 394 | except StopIteration: 395 | raise self.model_class.DoesNotExist('instance matching query does not exist:\nSQL: %s\nPARAMS: %s' % ( 396 | self.sql() 397 | )) 398 | 399 | class AsyncModel(Model): 400 | 401 | @classmethod 402 | def select(cls, *selection): 403 | query = AsyncSelectQuery(cls, *selection) 404 | if cls._meta.order_by: 405 | query = query.order_by(*cls._meta.order_by) 406 | return query 407 | 408 | @classmethod 409 | def update(cls, **update): 410 | fdict = dict((cls._meta.fields[f], v) for f, v in update.items()) 411 | return AsyncUpdateQuery(cls, fdict) 412 | 413 | @classmethod 414 | def insert(cls, **insert): 415 | fdict = dict((cls._meta.fields[f], v) for f, v in insert.items()) 416 | return AsyncInsertQuery(cls, fdict) 417 | 418 | @classmethod 419 | def delete(cls): 420 | return AsyncDeleteQuery(cls) 421 | 422 | @classmethod 423 | def raw(cls, sql, *params): 424 | return AsyncRawQuery(cls, sql, *params) 425 | 426 | @classmethod 427 | def create(cls, **query): 428 | callback = query.pop('callback', None) 429 | 430 | inst = cls(**query) 431 | inst.save(force_insert=True, callback=callback) 432 | 433 | 434 | # @classmethod 435 | # def get(cls, *query, **kwargs): 436 | # sq = cls.select().naive() 437 | # if query: 438 | # sq = sq.where(*query) 439 | # if kwargs: 440 | # sq = sq.filter(**kwargs) 441 | # return sq.get 442 | 443 | @classmethod 444 | def get_or_create(cls, **kwargs): 445 | callback = kwargs.pop('callback', None) 446 | 447 | sq = cls.select().filter(**kwargs) 448 | try: 449 | return sq.get(callback=callback) 450 | except cls.DoesNotExist: 451 | return cls.create(callback=callback, **kwargs) 452 | 453 | 454 | @classmethod 455 | @gen.engine 456 | def table_exists(cls, callback=None): 457 | 458 | tables = yield gen.Task(cls._meta.database.get_tables) 459 | 460 | if callback: 461 | callback(cls._meta.db_table in tables) 462 | 463 | @classmethod 464 | @gen.engine 465 | def create_table(cls, fail_silently=False, callback=None): 466 | 467 | # yiele 468 | exists = yield gen.Task(cls.table_exists) 469 | if fail_silently and exists: 470 | return 471 | 472 | db = cls._meta.database 473 | pk = cls._meta.primary_key 474 | if db.sequences and pk.sequence and not db.sequence_exists(pk.sequence): 475 | db.create_sequence(pk.sequence) 476 | 477 | cursor = yield gen.Task(db.create_table, cls) 478 | 479 | for field_name, field_obj in cls._meta.fields.items(): 480 | if isinstance(field_obj, ForeignKeyField): 481 | yield gen.Task(db.create_foreign_key, cls, field_obj) 482 | elif field_obj.index or field_obj.unique: 483 | yield gen.Task(db.create_index, cls, [field_obj], field_obj.unique) 484 | 485 | if cls._meta.indexes: 486 | for fields, unique in cls._meta.indexes: 487 | count = count + 1 488 | yield gen.Task(db.create_index, cls, fields, unique) 489 | 490 | if callback: 491 | callback(cursor) 492 | 493 | 494 | @classmethod 495 | def drop_table(cls, fail_silently=False, callback=None): 496 | cls._meta.database.drop_table(cls, fail_silently, callback=callback) 497 | 498 | @gen.engine 499 | def save(self, force_insert=False, only=None, callback=None): 500 | field_dict = dict(self._data) 501 | pk = self._meta.primary_key 502 | if only: 503 | field_dict = self._prune_fields(field_dict, only) 504 | 505 | if self.get_id() is not None and not force_insert: 506 | field_dict.pop(pk.name, None) 507 | update = self.update( 508 | **field_dict 509 | ).where(pk == self.get_id()) 510 | 511 | yield gen.Task(update.execute) 512 | if callback: 513 | callback(self.get_id()) 514 | 515 | else: 516 | if self._meta.auto_increment: 517 | field_dict.pop(pk.name, None) 518 | insert = self.insert(**field_dict) 519 | 520 | new_pk = yield gen.Task(insert.execute) 521 | if self._meta.auto_increment: 522 | self.set_id(new_pk) 523 | 524 | if callback: 525 | callback(new_pk) 526 | 527 | 528 | @gen.engine 529 | def delete_instance(self, recursive=False, delete_nullable=False, callback=None): 530 | if recursive: 531 | for query, fk in reversed(list(self.dependencies(delete_nullable))): 532 | if fk.null and not delete_nullable: 533 | yield genTask(fk.model_class.update(**{fk.name: None}).where(query).execute) 534 | else: 535 | yield genTask(fk.model_class.delete().where(query).execute) 536 | yield genTask(self.delete().where(self._meta.primary_key == self.get_id()).execute) 537 | 538 | if callback: 539 | callback() 540 | 541 | # test 542 | if __name__ == '__main__': 543 | from tornado import gen 544 | from tornado.ioloop import IOLoop 545 | from tornado.httpserver import HTTPServer 546 | from tornado.web import asynchronous, RequestHandler, Application 547 | 548 | database = PostgresqlAsyncDatabase('test', 549 | user = 'vfasky', 550 | host = '127.0.0.1', 551 | password = '19851024', 552 | size = 20, 553 | ) 554 | 555 | class User(AsyncModel): 556 | class Meta: 557 | database = database 558 | 559 | name = CharField() 560 | password = CharField(max_length = 255) 561 | 562 | database.connect() 563 | 564 | 565 | class AsyncHandler(RequestHandler): 566 | @asynchronous 567 | @gen.engine 568 | def get(self): 569 | exists = yield gen.Task(User.table_exists) 570 | if not exists: 571 | yield gen.Task(User.create_table) 572 | 573 | # add 574 | user = User() 575 | user.name = 'test3' 576 | user.password = '5677' 577 | 578 | yield gen.Task(user.save) 579 | print user.id 580 | 581 | # edit 582 | user = yield gen.Task(User.get(User.id == 9)) 583 | user.name = 'test03' 584 | ret = yield gen.Task(user.save) 585 | print ret 586 | 587 | # count 588 | count = yield gen.Task(User.select(User.name).where(User.name == 'test03').count) 589 | print count 590 | 591 | # delete 592 | ret = yield gen.Task(User.delete().where(User.name % 'test%').execute) 593 | print ret 594 | 595 | # user = yield gen.Task(User.select().where(User.name == 'wing').get) 596 | # self.write(user.name) 597 | self.finish() 598 | 599 | application = Application([ 600 | (r'/', AsyncHandler), 601 | ], debug=True) 602 | 603 | http_server = HTTPServer(application) 604 | http_server.listen(8181) 605 | IOLoop.instance().start() 606 | -------------------------------------------------------------------------------- /plugins.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2013-05-28 11:38:23 4 | # @Author : vfasky (vfasky@gmail.com) 5 | # @Link : http://vfasky.com 6 | # @Version : $Id$ 7 | 8 | __all__ = [ 9 | 'get_work_names', 10 | 'get_list', 11 | 'get_config', 12 | 'init', 13 | 'reset', 14 | ] 15 | 16 | import functools 17 | from mopee import AsyncModel, CharField, TextField 18 | from utils import Json 19 | from tornado import gen 20 | from tornado.util import import_object 21 | from tornado.web import UIModule 22 | 23 | # 绑定的 app 对象 24 | _application = None 25 | # 激活的插件 26 | _work_plugins = [] 27 | # 插件配置 28 | _config = {} 29 | # 可用插件列表 30 | _list = {} 31 | 32 | # 缓放缓存的key 33 | _cache_key = '__app.plugins__' 34 | 35 | def init(method): 36 | # 插件初始化 37 | 38 | @functools.wraps(method) 39 | @gen.engine 40 | def wrapper(self, **settings): 41 | global _application 42 | _application = self 43 | 44 | if _application.cache: 45 | yield gen.Task(reset) 46 | 47 | method(self, **settings) 48 | 49 | return wrapper 50 | 51 | @gen.engine 52 | def reset(callback=None): 53 | # 重置插件 54 | global _list , _config , _work_plugins 55 | 56 | _work_plugins = [] 57 | _config = {} 58 | _list = {} 59 | 60 | plugin_configs = yield gen.Task(_application.cache.get, _cache_key, []) 61 | 62 | for plugin_data in plugin_configs: 63 | plugin_name = plugin_data.get('name') 64 | _work_plugins.append(plugin_name) 65 | _config[plugin_name] = plugin_data.get('config', {}) 66 | plugin = import_object(str(plugin_name)) 67 | 68 | if _application: 69 | # 绑定 ui_modules 70 | for v in plugin_data.get('ui_modules', []): #Json.decode(plugin_ar.ui_modules): 71 | _application.ui_modules[v.__name__] = import_object(str(v)) 72 | 73 | # 绑定 header 74 | for v in plugin_data.get('handlers', []): #Json.decode(plugin_ar.handlers): 75 | plugin_module = v.split('.handlers.')[0] + '.handlers' 76 | 77 | if plugin_module not in sys.modules.keys() : 78 | import_object(str(plugin_module)) 79 | else: 80 | reload(import_object(str(plugin_module))) 81 | 82 | binds = plugin_data.get('bind', {}) 83 | for event in binds: 84 | _list.setdefault(event,[]) 85 | for v in binds[event]: 86 | v['handler'] = plugin 87 | v['name'] = plugin_name 88 | _list[event].append(v) 89 | 90 | 91 | if callback: 92 | callback(True) 93 | 94 | 95 | 96 | # 取激活的插件名 97 | def get_work_names(): 98 | return _work_plugins 99 | 100 | # 取可用插件列表 101 | def get_list(): 102 | return _list 103 | 104 | # 取插件的配置 105 | def get_config(plugin_name, default = {}): 106 | return _config.get(plugin_name,default) 107 | 108 | # 设置插件配置 109 | @gen.engine 110 | def set_config(plugin_name, config, callback=None): 111 | global _config 112 | plugin_name = plugin_name.strip() 113 | plugin_configs = yield gen.Task(_application.cache.get, _cache_key, None) 114 | 115 | if not plugin_configs: 116 | if callback: 117 | callback(False) 118 | return 119 | 120 | is_save = False 121 | for plugin_data in plugin_configs: 122 | if plugin_data.get('name') == plugin_name: 123 | plugin_data['config'] = config 124 | is_save = True 125 | break 126 | 127 | if is_save: 128 | yield gen.Task(_application.cache.set, _cache_key, plugin_configs) 129 | yield gen.Task(_application.sync_ping) 130 | 131 | if callback: 132 | callback(is_save) 133 | 134 | 135 | ''' 136 | 调用对应的插件 137 | ''' 138 | def call(event, that): 139 | target = that.__class__.__module__ + '.' + that.__class__.__name__ 140 | handlers = [] 141 | target = target.split('handlers.').pop() 142 | 143 | for v in get_list().get(event,[]): 144 | if v['target'].find('*') == -1 and v['target'] == target: 145 | handlers.append(v) 146 | else: 147 | key = v['target'].split('*')[0] 148 | if target.find(key) == 0 or v['target'] == '*' : 149 | handlers.append(v) 150 | return handlers 151 | 152 | class Events(object): 153 | ''' 154 | handler 事件绑定 155 | ''' 156 | 157 | 158 | @staticmethod 159 | def on_init(method): 160 | ''' 161 | 控制器初始化时执行 162 | ''' 163 | 164 | @functools.wraps(method) 165 | @gen.engine 166 | def wrapper(self): 167 | handlers = call('on_init', self) 168 | is_run = True 169 | for v in handlers: 170 | plugin = v['handler']() 171 | # 设置上下文 172 | plugin._context = { 173 | 'self' : self , 174 | } 175 | ret = yield gen.Task(getattr(plugin,v['callback'])) 176 | if False == ret: 177 | is_run = False 178 | break 179 | 180 | if is_run: 181 | method(self) 182 | 183 | return wrapper 184 | 185 | # 控制器执行前调用 186 | @staticmethod 187 | def before_execute(method): 188 | 189 | @functools.wraps(method) 190 | @gen.engine 191 | def wrapper(self, transforms, *args, **kwargs): 192 | self._transforms = transforms 193 | 194 | handlers = call('before_execute', self) 195 | is_run = True 196 | for v in handlers: 197 | plugin = v['handler']() 198 | # 设置上下文 199 | plugin._context = { 200 | 'transforms' : transforms, 201 | 'args' : args, 202 | 'kwargs' : kwargs, 203 | 'self' : self 204 | } 205 | 206 | ret = yield gen.Task(getattr(plugin,v['callback'])) 207 | if False == ret: 208 | is_run = False 209 | break 210 | 211 | transforms = plugin._context['transforms'] 212 | args = plugin._context['args'] 213 | kwargs = plugin._context['kwargs'] 214 | 215 | if is_run: 216 | method(self, transforms, *args, **kwargs) 217 | 218 | return wrapper 219 | 220 | # 渲染模板前调用 221 | @staticmethod 222 | def before_render(method): 223 | 224 | @functools.wraps(method) 225 | @gen.engine 226 | def wrapper(self, template_name, **kwargs): 227 | handlers = call('before_render', self) 228 | is_run = True 229 | for v in handlers: 230 | plugin = v['handler']() 231 | # 设置上下文 232 | plugin._context = { 233 | 'template_name' : template_name , 234 | 'kwargs' : kwargs , 235 | 'self' : self 236 | } 237 | ret = yield gen.Task(getattr(plugin,v['callback'])) 238 | if False == ret: 239 | is_run = False 240 | break 241 | 242 | template_name = plugin._context['template_name'] 243 | kwargs = plugin._context['kwargs'] 244 | 245 | if is_run: 246 | method(self, template_name, **kwargs) 247 | 248 | return wrapper 249 | 250 | # 完成http请求时调用 251 | @staticmethod 252 | def on_finish(method): 253 | 254 | @functools.wraps(method) 255 | @gen.engine 256 | def wrapper(self): 257 | handlers = call('on_finish', self) 258 | is_run = True 259 | for v in handlers: 260 | plugin = v['handler']() 261 | # 设置上下文 262 | plugin._context = { 263 | 'self' : self , 264 | } 265 | ret = yield gen.Task(getattr(plugin,v['callback'])) 266 | if False == ret: 267 | is_run = False 268 | break 269 | 270 | if is_run: 271 | method(self) 272 | 273 | return wrapper 274 | 275 | 276 | def format_doc(cls): 277 | # 格式化 __doc__ 278 | doc = cls.__doc__ 279 | title = cls.__name__ 280 | link = '' 281 | if doc.find('@title') != -1: 282 | title = doc.split('@title').pop().split('@')[0] 283 | doc = doc.replace('@title' + title , '') 284 | title = title.strip() 285 | 286 | if doc.find('@link') != -1: 287 | link = doc.split('@link').pop().split('@')[0] 288 | doc = doc.replace('@link' + link , '') 289 | link = link.strip() 290 | 291 | return { 292 | 'txt' : doc , 293 | 'title' : title , 294 | 'link' : link 295 | } 296 | 297 | # 安装插件 298 | @gen.engine 299 | def install(plugin_name, config=None, callback=None): 300 | register = import_object(str(plugin_name).strip() + '.register') 301 | 302 | name = register._handler.__module__ + \ 303 | '.' + register._handler.__name__ 304 | 305 | plugin_configs = yield gen.Task(_application.cache.get, _cache_key, []) 306 | 307 | for plugin_data in plugin_configs: 308 | if plugin_data.get('name') == name: 309 | if callback: 310 | callback(False) 311 | return 312 | 313 | plugin = import_object(name)() 314 | plugin.install() 315 | 316 | # 尝试自加加载 ui_modules.py 317 | try: 318 | ui_modules = import_object(plugin_name + '.uimodules') 319 | for v in dir(ui_modules): 320 | if issubclass(getattr(ui_modules,v), UIModule) \ 321 | and v != 'UIModule': 322 | plugin.add_ui_module(v) 323 | except Exception, e: 324 | pass 325 | 326 | # 尝试自加加载 handlers.py 327 | try: 328 | handlers = import_object(plugin_name + '.handlers') 329 | reload(handlers) 330 | for v in dir(handlers): 331 | 332 | if issubclass(getattr(handlers,v), RequestHandler) \ 333 | and v != 'RequestHandler': 334 | 335 | plugin.add_handler(v) 336 | except Exception, e: 337 | pass 338 | 339 | 340 | handlers = [] 341 | for v in plugin._handlers: 342 | handlers.append( 343 | v.__module__ + '.' + v.__name__ 344 | ) 345 | 346 | ui_modules = [] 347 | for v in plugin._ui_modules: 348 | ui_modules.append( 349 | v.__module__ + '.' + v.__name__ 350 | ) 351 | 352 | pl = {} 353 | pl['name'] = name 354 | pl['bind'] = register._targets 355 | pl['handlers'] = handlers 356 | pl['ui_modules'] = ui_modules 357 | pl['config'] = {} 358 | if config: 359 | pl['config'] = config 360 | elif plugin.form : 361 | pl['config']= plugin.form().data 362 | 363 | plugin_configs.append(pl) 364 | 365 | yield gen.Task(_application.cache.set, _cache_key, plugin_configs) 366 | 367 | 368 | if _application: 369 | yield gen.Task(_application.sync_ping) 370 | 371 | if callback: 372 | callback(True) 373 | 374 | 375 | 376 | # 卸载插件 377 | @gen.engine 378 | def uninstall(plugin_name, callback=None): 379 | register = import_object(str(plugin_name).strip() + '.register') 380 | 381 | name = register._handler.__module__ + \ 382 | '.' + register._handler.__name__ 383 | 384 | plugin_configs = yield gen.Task(_application.cache.get, _cache_key, []) 385 | 386 | for plugin_data in plugin_configs: 387 | if plugin_data.get('name') == name: 388 | plugin_configs.remove(plugin_data) 389 | 390 | yield gen.Task(_application.cache.set, _cache_key, plugin_configs) 391 | 392 | # 通知 application 同步 393 | if _application: 394 | yield gen.Task(_application.sync_ping) 395 | 396 | if callback: 397 | callback(True) 398 | 399 | return 400 | 401 | if callback: 402 | callback(False) 403 | return 404 | 405 | 406 | 407 | class Register(object): 408 | ''' 409 | 插件注册表 410 | ''' 411 | 412 | def __init__(self): 413 | self._handler = False 414 | self._targets = {} 415 | self._events = ( 416 | 'on_init' , 417 | 'before_execute' , 418 | 'before_render' , 419 | 'on_finish' , 420 | ) 421 | 422 | # 注册对象 423 | def handler(self): 424 | def decorator(handler): 425 | self._handler = handler 426 | return handler 427 | return decorator 428 | 429 | # 绑定事件 430 | def bind(self, event, targets): 431 | def decorator(func): 432 | if event in self._events: 433 | self._targets.setdefault(event,[]) 434 | for v in targets : 435 | self._targets[event].append({ 436 | 'target' : v , 437 | 'callback' : func.__name__ 438 | }) 439 | return func 440 | return decorator 441 | 442 | 443 | class Base(object): 444 | """ 445 | 插件的基类 446 | """ 447 | 448 | # 配置表单定义 449 | form = None 450 | 451 | def __init__(self): 452 | 453 | self.module = self.__class__.__module__ 454 | 455 | self.full_name = self.module + '.' + self.__class__.__name__ 456 | 457 | 458 | 459 | # 运行时的上下文 460 | self._context = {} 461 | 462 | # 插件的控制器 463 | self._handlers = [] 464 | 465 | # ui modules 466 | self._ui_modules = [] 467 | 468 | 469 | ''' 470 | 安装时执行 471 | 472 | ''' 473 | def install(self): 474 | pass 475 | 476 | ''' 477 | 卸载时执行 478 | ''' 479 | def uninstall(self): 480 | pass 481 | 482 | # 取配置 483 | @property 484 | def config(self): 485 | return get_config(self.full_name , {}) 486 | 487 | def set_config(self, config): 488 | set_config(self.full_name, config) 489 | 490 | ''' 491 | 添加控制器 492 | ''' 493 | def add_handler(self, handler): 494 | handler = self.module + '.handlers.' + handler 495 | handler = import_object(handler) 496 | if handler not in self._handlers: 497 | self._handlers.append(handler) 498 | 499 | ''' 500 | 添加 UI models 501 | ''' 502 | def add_ui_module(self, ui_module): 503 | ui_module = self.module + '.uimodules.' + ui_module 504 | ui_module = import_object(ui_module) 505 | if ui_module not in self._ui_modules: 506 | self._ui_modules.append(ui_module) 507 | 508 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Momoko==1.0.0 2 | psycopg2==2.5 3 | tornado==3.0.1 4 | wsgiref==0.1.2 5 | -------------------------------------------------------------------------------- /session.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date: 2013-05-10 14:36:13 4 | # @Author: vfasky (vfasky@gmail.com) 5 | # @Version: $Id$ 6 | 7 | ''' 8 | session 9 | ''' 10 | 11 | __all__ = [ 12 | 'Base', 13 | 'Mongod', 14 | ] 15 | 16 | import uuid 17 | import time 18 | import asyncmongo 19 | 20 | from tornado import gen 21 | 22 | 23 | class Base(object): 24 | ''' 25 | 异步 session 基类 26 | ''' 27 | 28 | 29 | def __init__(self, session_id = False, **settings): 30 | if False == session_id: 31 | session_id = str(uuid.uuid4()) 32 | 33 | self.settings = settings 34 | self.session_id = session_id 35 | self.left_time = int(settings.get('left_time', 1800)) 36 | self.storage = self.get_storage() 37 | 38 | 39 | @property 40 | def id(self): 41 | # 返回 session id 42 | return self.session_id 43 | 44 | def get_storage(self): 45 | pass 46 | 47 | def get_all(self, callback=None): 48 | def _remove_callback(ret): 49 | callback({}) 50 | 51 | def _update_callback(value): 52 | data = value.get('data', {}) 53 | callback(data) 54 | 55 | def _callback(value): 56 | if not value: 57 | return callback({}) 58 | 59 | this_time = int(time.time()) 60 | cache_life = value.get('time', 0) + self.left_time 61 | if cache_life < this_time: 62 | # 缓存已经失效 63 | self.clear(callback=_remove_callback) 64 | elif (cache_life - this_time) > (self.left_time / 2): 65 | # 缓存周期已经超过生存周期的一半,更新时间周期 66 | self.storage.set(value.get('data', {}), callback=_update_callback) 67 | else: 68 | data = value.get('data', {}) 69 | callback(data) 70 | 71 | self.storage.get(callback=_callback) 72 | 73 | def set(self, key, value, callback=None): 74 | def _callback(data): 75 | data[key] = value 76 | self.storage.set(data, callback) 77 | 78 | self.get_all(_callback) 79 | 80 | def get(self, key, default=None, callback=None): 81 | def _callback(data): 82 | 83 | if not data: 84 | return callback(default) 85 | 86 | callback(data.get(key, default)) 87 | 88 | self.get_all(callback=_callback) 89 | 90 | def remove(self, key, callback=None): 91 | def _set_callback(data): 92 | if data: 93 | callback(True) 94 | else: 95 | callback(False) 96 | 97 | def _callback(data): 98 | if not data: 99 | return callback(False) 100 | 101 | if data.has_key(key): 102 | del data[key] 103 | self.storage.set(data, _set_callback) 104 | else: 105 | callback(False) 106 | 107 | self.get_all(callback=_callback) 108 | 109 | def clear(self, callback=None): 110 | self.storage.remove(callback) 111 | 112 | class Mongod(Base): 113 | """"基于Mongod的session""" 114 | 115 | class Storage(object): 116 | 117 | def __init__(self, conn, table, session_id, left_time): 118 | self._conn = conn 119 | self._table = self._conn[table] 120 | self.session_id = session_id 121 | self.left_time = left_time 122 | self.where = {'session_id': session_id} 123 | 124 | def get(self, callback=None): 125 | def _callback(value, error): 126 | if callback: 127 | if error: 128 | raise Error(error) 129 | if value: 130 | callback(value) 131 | else: 132 | callback(None) 133 | 134 | self._table.find_one(self.where, callback=_callback) 135 | 136 | def remove(self, callback=None): 137 | def _callback(data, error): 138 | if callback: 139 | if error: 140 | raise Error(error) 141 | 142 | if callback: 143 | callback(len(data) == 1) 144 | 145 | self._table.remove(self.where, callback=_callback) 146 | 147 | 148 | @gen.engine 149 | def set(self, value, callback=None): 150 | session_data = { 151 | 'session_id' : self.session_id, 152 | 'data' : value, 153 | 'time' : int(time.time()) 154 | } 155 | 156 | def _callback(data, error): 157 | if callback: 158 | if error: 159 | raise Error(error) 160 | 161 | if callback: 162 | callback(session_data) 163 | 164 | ret, error = yield gen.Task(self._table.find_one, self.where) 165 | data = ret[0] 166 | 167 | if not data or len(data) == 0: 168 | self._table.insert(session_data, callback=_callback) 169 | else: 170 | self._table.update({ 171 | '_id' : data['_id'] 172 | }, session_data, upsert=True, safe=True, callback=_callback) 173 | 174 | def get_storage(self): 175 | kwargs = self.settings 176 | conn = asyncmongo.Client( 177 | pool_id = kwargs.get('pool_id', 'xcat.session.Mongod'), 178 | host = kwargs.get('host', '127.0.0.1'), 179 | port = kwargs.get('port', 27017), 180 | maxcached = kwargs.get('maxcached', 10), 181 | maxconnections = kwargs.get('maxconnections', 50), 182 | dbname = kwargs.get('dbname', 'session'), 183 | dbuser = kwargs.get('dbuser', None), 184 | dbpass = kwargs.get('dbpass', None) 185 | ) 186 | 187 | table = kwargs.get('table', 'sessions') 188 | 189 | return self.Storage(conn, table, self.session_id, self.left_time) 190 | 191 | ''' 192 | 测试 and 用法 193 | ''' 194 | if __name__ == '__main__': 195 | from tornado.ioloop import IOLoop 196 | from tornado.httpserver import HTTPServer 197 | #from tornado.options import parse_command_line 198 | from tornado.web import asynchronous, RequestHandler, Application 199 | 200 | class Handler(RequestHandler): 201 | 202 | def initialize(self): 203 | key = 'PYSESSID' 204 | 205 | if self.get_secure_cookie(key): 206 | self.session = Mongod(self.get_secure_cookie(key)) 207 | else: 208 | session = Mongod(str(uuid.uuid4())) 209 | self.set_secure_cookie(key , session.id) 210 | self.session = session 211 | 212 | 213 | @asynchronous 214 | @gen.engine 215 | def get(self): 216 | ret = yield gen.Task(self.session.set, 'test2', {'hello': 'word'}) 217 | print ret 218 | data = yield gen.Task(self.session.get, 'test2') 219 | print data 220 | ret = yield gen.Task(self.session.remove, 'test2') 221 | print ret 222 | ret = yield gen.Task(self.session.clear) 223 | print ret 224 | 225 | self.finish() 226 | 227 | application = Application([ 228 | (r'/', Handler), 229 | ], debug=True, cookie_secret="fsfwo#@(sfk") 230 | 231 | http_server = HTTPServer(application) 232 | http_server.listen(8181) 233 | IOLoop.instance().start() 234 | 235 | -------------------------------------------------------------------------------- /utils.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2013-05-27 16:49:28 4 | # @Author : vfasky (vfasky@gmail.com) 5 | # @Link : http://vfasky.com 6 | # @Version : $Id$ 7 | 8 | __all__ = [ 9 | 'md5', 10 | 'Json', 11 | 'Date', 12 | 'Filters', 13 | 'Validators', 14 | ] 15 | 16 | 17 | import types 18 | import re 19 | import time 20 | import hashlib 21 | import sys 22 | from HTMLParser import HTMLParser 23 | from tornado import escape 24 | 25 | 26 | def md5(s): 27 | m = hashlib.md5(str(s)) 28 | m.digest() 29 | return m.hexdigest() 30 | 31 | class Json: 32 | 33 | @staticmethod 34 | def decode(s,default=[]): 35 | s = Filters.trim(str(s)) 36 | if '' == s: 37 | return default 38 | try: 39 | return escape.json_decode(s) 40 | except Exception, e: 41 | return default 42 | 43 | 44 | @staticmethod 45 | def encode(json): 46 | return escape.json_encode(json) 47 | 48 | 49 | 50 | class Filters: 51 | 52 | """ 53 | 过滤字符 54 | ============= 55 | 56 | #### 方法: 57 | 58 | - trim 去除两边空格 59 | - to_number 转换成数字 60 | - to_text 转换成纯文本 61 | #- to_json 转换成json 62 | 63 | """ 64 | 65 | @staticmethod 66 | def trim(s): 67 | return str(s).strip() 68 | 69 | @staticmethod 70 | def to_number(s): 71 | if Validators.is_number(s): 72 | return int(s) 73 | if Validators.is_float(s): 74 | return float(s) 75 | return 0 76 | 77 | @staticmethod 78 | def to_time(x): 79 | arr = str(x).split(':') 80 | if len(arr) == 2: 81 | # 小时 82 | hour = int(arr[0]) 83 | # 分钟 84 | minute = int(arr[1]) 85 | if 0 > hour or hour > 23: 86 | return '00:00' 87 | 88 | if 1 > minute or minute > 59: 89 | return '00:00' 90 | 91 | hour = str(hour) 92 | if int(hour) < 10: 93 | hour = '0' + hour 94 | 95 | minute = str(minute) 96 | if int(minute) < 10: 97 | minute = '0' + minute 98 | 99 | return '%s:%s' % (hour, minute) 100 | 101 | return '00:00' 102 | 103 | # @staticmethod 104 | # def to_json(s): 105 | # return Json.encode(s) 106 | 107 | 108 | @staticmethod 109 | def to_text(s): 110 | if None == s : return None 111 | html = s.strip() 112 | html = html.strip("\n") 113 | result = [] 114 | parser = HTMLParser() 115 | parser.handle_data = result.append 116 | parser.feed(html) 117 | parser.close() 118 | return ''.join(result) 119 | 120 | 121 | 122 | 123 | class Validators: 124 | ''' 125 | 验证类 126 | ============= 127 | 128 | #### 方法: 129 | 130 | - is_string 是否字符 131 | - is_number 是否数字 132 | - is_float 是否浮点数 133 | - is_dict 是否字典 134 | - is_array 是否数组 135 | - is_empty 是否为空(含None) 136 | - is_date 是否符合日历规则 2010-01-31 137 | - is_email 是否邮件地址 138 | - is_chinese_char_string 是否为中文字符串 139 | - is_legal_accounts 是否合法 字母开头,允许4-16字节,允许字母数字下划线 140 | - is_ip_addr 是否ip地址 141 | 142 | ''' 143 | 144 | @staticmethod 145 | def is_string(x): 146 | return type(x) is types.StringType 147 | 148 | @staticmethod 149 | def is_number(x): 150 | rule = '[+-]?\d+$' 151 | return re.match(rule, str(x)) 152 | 153 | #判断是否为浮点数 1.324 154 | @staticmethod 155 | def is_float(x): 156 | return type(x) is types.FloatType 157 | 158 | #判断是否为字典 {'a1':'1','a2':'2'} 159 | @staticmethod 160 | def is_dict(x): 161 | return type(x) is types.DictType 162 | 163 | @staticmethod 164 | def is_array(x): 165 | return type(x) is types.ListType 166 | 167 | @staticmethod 168 | def is_empty(x): 169 | if type(x) is types.NoneType: 170 | return True 171 | if Validators.is_number(x): 172 | return False 173 | return len(x) == 0 174 | 175 | #判断是否为日期格式,并且是否符合日历规则 2010-01-31 176 | @staticmethod 177 | def is_date(x): 178 | x = str(x) 179 | if len(x) == 10: 180 | rule = '(([0-9]{3}[1-9]|[0-9]{2}[1-9][0-9]{1}|[0-9]{1}[1-9][0-9]{2}|[1-9][0-9]{3})-(((0[13578]|1[02])-(0[1-9]|[12][0-9]|3[01]))|((0[469]|11)-(0[1-9]|[12][0-9]|30))|(02-(0[1-9]|[1][0-9]|2[0-8]))))|((([0-9]{2})(0[48]|[2468][048]|[13579][26])|((0[48]|[2468][048]|[3579][26])00))-02-29)$/' 181 | match = re.match( rule , x ) 182 | if match: 183 | return True 184 | return False 185 | return False 186 | 187 | 188 | 189 | #判断是否为邮件地址 190 | @staticmethod 191 | def is_email(x): 192 | x = str(x) 193 | rule = '[\w-]+(\.[\w-]+)*@[\w-]+(\.[\w-]+)+$' 194 | match = re.match( rule , x ) 195 | 196 | if match: 197 | return True 198 | return False 199 | 200 | #判断是否为中文字符串 201 | @staticmethod 202 | def is_chinese_char_string(x): 203 | x = str(x) 204 | for v in x: 205 | if (v >= u"\u4e00" and v<=u"\u9fa5") or (v >= u'\u0041' and v<=u'\u005a') or (v >= u'\u0061' and v<=u'\u007a'): 206 | continue 207 | else: 208 | return False 209 | return True 210 | 211 | #判断帐号是否合法 字母开头,允许4-16字节,允许字母数字下划线 212 | @staticmethod 213 | def is_legal_accounts(x): 214 | x = str(x) 215 | rule = '[a-zA-Z][a-zA-Z0-9_]{3,15}$' 216 | match = re.match( rule , x ) 217 | 218 | if match: 219 | return True 220 | return False 221 | 222 | #匹配IP地址 223 | @staticmethod 224 | def is_ip_addr(x): 225 | x = str(x) 226 | #rule = '\d+\.\d+\.\d+\.\d+' 227 | rule = '((2[0-4]\d|25[0-5]|[01]?\d\d?)\.){3}(2[0-4]\d|25[0-5]|[01]?\d\d?)' 228 | match = re.match( rule , x ) 229 | 230 | if match: 231 | return True 232 | return False 233 | 234 | -------------------------------------------------------------------------------- /web.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | # @Date : 2013-05-27 16:54:47 4 | # @Author : vfasky (vfasky@gmail.com) 5 | # @Link : http://vfasky.com 6 | # @Version : $Id$ 7 | 8 | __all__ = [ 9 | 'acl', 10 | 'route', 11 | 'session', 12 | 'Application', 13 | 'RequestHandler' 14 | ] 15 | import copy 16 | import time 17 | import functools 18 | import session as Xsession 19 | import utils 20 | import plugins 21 | import config 22 | import cache 23 | import uuid 24 | import re 25 | import os 26 | import arrow 27 | import types 28 | from tornado.web import url, RequestHandler, \ 29 | StaticFileHandler, Application, asynchronous 30 | from tornado.escape import linkify 31 | from tornado import gen 32 | from tornado.options import options, define 33 | from tornado.util import import_object 34 | from jinja2 import Environment, FileSystemLoader 35 | from .utils import Validators 36 | 37 | def form(form_name): 38 | '''表单加载器''' 39 | 40 | def load(method): 41 | @functools.wraps(method) 42 | def wrapper(self, *args, **kwargs): 43 | if form_name.find('.') == 0 : 44 | module_name = self.__class__.__module__.split('.') 45 | module_name.pop() 46 | form_class = '.'.join(module_name) + form_name 47 | else: 48 | form_class = form_name 49 | 50 | locale_code = 'en_US' 51 | if hasattr(self, 'locale') and hasattr(self.locale, 'code'): 52 | locale_code = self.locale.code 53 | self.form = import_object(form_class)(self.request.arguments, locale_code) 54 | self.form.xsrf_form_html = self.xsrf_form_html 55 | return method(self, *args, **kwargs) 56 | 57 | return wrapper 58 | 59 | return load 60 | 61 | def session(method): 62 | ''' 63 | 异步 session 的绑定 64 | ''' 65 | @asynchronous 66 | @functools.wraps(method) 67 | def wrapper(self, *args, **kwargs): 68 | if hasattr(self, '_session'): 69 | return method(self, *args, **kwargs) 70 | 71 | settings = config.get('session', False) 72 | 73 | if settings: 74 | session_name = settings.get('name', 'PYSESSID') 75 | session_storage = settings.get('storage', 'Mongod') 76 | session_config = settings.get('config', {}) 77 | 78 | if hasattr(Xsession, session_storage): 79 | 80 | Session = getattr(Xsession, session_storage) 81 | 82 | if self.get_secure_cookie(session_name): 83 | self._session = Session(self.get_secure_cookie(session_name), **session_config) 84 | else: 85 | session = Session(**session_config) 86 | self.set_secure_cookie(session_name , session.id) 87 | self._session = session 88 | 89 | def none_callback(*args, **kwargs): 90 | pass 91 | 92 | def finish(self, *args, **kwargs): 93 | 94 | super(self.__class__, self).finish(*args, **kwargs) 95 | if self._session_cache != self.session: 96 | #print 'change session' 97 | if self.session: 98 | #print 'save session' 99 | self._session.storage.set(self.session, none_callback) 100 | else: 101 | #print 'clear session' 102 | self._session.clear() 103 | 104 | @gen.engine 105 | def init(self): 106 | data = yield gen.Task(self._session.get_all) 107 | self._session_cache = copy.copy(data) 108 | self.session = data 109 | self.finish = functools.partial(finish, self) 110 | method(self, *args, **kwargs) 111 | 112 | init(self) 113 | else: 114 | method(self, *args, **kwargs) 115 | 116 | return wrapper 117 | 118 | def acl(method): 119 | ''' 120 | 访问控制 121 | ========== 122 | 123 | ## 特殊标识: 124 | 125 | - ACL_NO_ROLE 没有角色用户 126 | - ACL_HAS_ROLE 有角色用户 127 | 128 | ''' 129 | 130 | # 检查 131 | def check(rule, roles): 132 | if rule.get('deny', False): 133 | for r in roles: 134 | if r in rule['deny']: 135 | return False 136 | 137 | if rule.get('allow', False): 138 | for r in roles: 139 | if r in rule['allow']: 140 | return True 141 | 142 | return False 143 | 144 | # 取当前用户角色 145 | @session 146 | def get_roles(self, callback=None): 147 | # 当前用户 148 | current_user = self.current_user 149 | #print 'acl check' 150 | 151 | # 格式化角色 152 | roles = [] 153 | if None == current_user: 154 | roles.append('ACL_NO_ROLE') 155 | 156 | elif utils.Validators.is_dict(current_user): 157 | if False == ('roles' in current_user) \ 158 | or 0 == len(current_user['roles']): 159 | 160 | roles.append('ACL_NO_ROLE') 161 | else: 162 | roles.append('ACL_HAS_ROLE') 163 | 164 | for r in current_user['roles']: 165 | roles.append(r) 166 | if callback: 167 | callback(roles) 168 | 169 | 170 | @functools.wraps(method) 171 | def wrapper(self, transforms, *args, **kwargs): 172 | # 告诉浏览器不要缓存 173 | self.set_header('Pragma', 'no-cache') 174 | self.set_header('Expires', -1) 175 | 176 | # 唯一标识 177 | URI = self.__class__.__module__ + '.' + self.__class__.__name__ 178 | # 访问规则 179 | rules = self.settings.get('acls', []) 180 | 181 | if len(rules) == 0: 182 | return method(self, transforms, *args, **kwargs) 183 | 184 | @gen.engine 185 | def init(): 186 | roles = False 187 | 188 | for r in rules: 189 | if r['URI'] == URI: 190 | if False == roles: 191 | roles = yield gen.Task(get_roles, self) 192 | #print roles 193 | 194 | if False == check(r, roles): 195 | self._transforms = transforms 196 | self.on_access_denied() 197 | return #self.finish() 198 | 199 | 200 | method(self, transforms, *args, **kwargs) 201 | 202 | init() 203 | 204 | return wrapper 205 | 206 | 207 | class Route(object): 208 | """ 209 | extensions.route 210 | 211 | Example: 212 | 213 | @route(r'/', name='index') 214 | class IndexHandler(tornado.web.RequestHandler): 215 | pass 216 | 217 | class Application(tornado.web.Application): 218 | def __init__(self): 219 | handlers = [ 220 | # ... 221 | ] + Route.routes() 222 | 223 | @link https://github.com/laoqiu/pypress-tornado/blob/master/pypress/extensions/routing.py 224 | """ 225 | 226 | # 路由信息 227 | _routes = {} 228 | # 访问规则 229 | _acl = [] 230 | 231 | def __init__(self, pattern, name=None, host='.*$', allow=None, deny=None, **kwargs): 232 | self.pattern = pattern 233 | self.kwargs = kwargs 234 | self.name = name 235 | self.host = host 236 | self.allow = allow 237 | self.deny = deny 238 | 239 | def __call__(self, handler_class): 240 | 241 | URI = handler_class.__module__ + '.' + handler_class.__name__ 242 | name = self.name or URI.split('.handlers.').pop() 243 | 244 | # acl 245 | allow = self.allow 246 | deny = self.deny 247 | 248 | if allow or deny: 249 | index = False 250 | for acl in self._acl: 251 | if acl['URI'] == URI: 252 | index = self._acl.index(acl) 253 | break 254 | 255 | if False == index: 256 | item = {'URI' : URI, 'allow' : [], 'deny' : []} 257 | self._acl.append(item) 258 | index = self._acl.index(item) 259 | 260 | if allow: 261 | for r in allow: 262 | if r not in self._acl[index]['allow']: 263 | self._acl[index]['allow'].append(r) 264 | 265 | if deny: 266 | for r in deny: 267 | if r not in self._acl[index]['deny']: 268 | self._acl[index]['deny'].append(r) 269 | 270 | spec = url(self.pattern, handler_class, self.kwargs, name=name) 271 | 272 | self._routes.setdefault(self.host, []) 273 | if spec not in self._routes[self.host]: 274 | self._routes[self.host].append(spec) 275 | 276 | # 存放路由规则 277 | if False == hasattr(handler_class, 'routes'): 278 | handler_class.routes = [] 279 | 280 | if len(handler_class.routes) > 0: 281 | if handler_class.routes[0]['URI'] != URI: 282 | handler_class.routes = [] 283 | 284 | handler_class.routes.append({ 285 | 'name': name, 286 | 'spec': spec, 287 | 'URI': URI 288 | }) 289 | return handler_class 290 | 291 | @classmethod 292 | def reset(cls): 293 | cls._acl = [] 294 | cls._routes = {} 295 | 296 | @classmethod 297 | def reset_handlers(cls,application): 298 | settings = application.settings 299 | 300 | # 重置 handlers 301 | if settings.get("static_path") : 302 | path = settings["static_path"] 303 | 304 | static_url_prefix = settings.get("static_url_prefix", 305 | "/static/") 306 | static_handler_class = settings.get("static_handler_class", 307 | StaticFileHandler) 308 | static_handler_args = settings.get("static_handler_args", {}) 309 | static_handler_args['path'] = path 310 | for pattern in [re.escape(static_url_prefix) + r"(.*)", 311 | r"/(favicon\.ico)", r"/(robots\.txt)"]: 312 | 313 | item = url(pattern, static_handler_class, static_handler_args) 314 | cls._routes.setdefault('.*$', []) 315 | if item not in cls._routes['.*$'] : 316 | cls._routes['.*$'].insert(0, item) 317 | 318 | # 404 319 | item = url(r"/(.+)$", _404Handler) 320 | 321 | if cls._routes.get('.*$') and item not in cls._routes['.*$'] : 322 | cls._routes['.*$'].append(item) 323 | 324 | application.handlers = [] 325 | application.named_handlers = {} 326 | 327 | 328 | @classmethod 329 | def acl(cls, application=None): 330 | if application: 331 | application.settings['acls'] = cls._acl 332 | else: 333 | return cls._acl 334 | 335 | @classmethod 336 | def routes(cls, application=None): 337 | if application: 338 | cls.reset_handlers(application) 339 | for host, handlers in cls._routes.items(): 340 | application.add_handlers(host, handlers) 341 | 342 | else: 343 | return reduce(lambda x,y:x+y, cls._routes.values()) if cls._routes else [] 344 | 345 | @classmethod 346 | def url_for(cls, name, *args): 347 | named_handlers = dict([(spec.name, spec) for spec in cls.routes() if spec.name]) 348 | if name in named_handlers: 349 | return named_handlers[name].reverse(*args) 350 | raise KeyError("%s not found in named urls" % name) 351 | 352 | route = Route 353 | 354 | def sync_app(method): 355 | ''' 356 | 同步各个app 357 | ''' 358 | 359 | @functools.wraps(method) 360 | @gen.engine 361 | def wrapper(self, request): 362 | if self.cache: 363 | sync_id = yield gen.Task(self.cache.get, self._sync_key, 0) 364 | #print sync_id 365 | if sync_id != self._sync_id: 366 | #print '同步' 367 | ret = yield gen.Task(self.sync, sync_id) 368 | method(self, request) 369 | 370 | return wrapper 371 | 372 | 373 | class Application(Application): 374 | 375 | def __init__(self, default_host="", transforms=None, 376 | wsgi=False, **settings): 377 | 378 | if settings.get('template_path'): 379 | # 配置 jinja2 380 | self.jinja_env = Environment( 381 | loader = FileSystemLoader(settings['template_path']), 382 | auto_reload = settings['debug'], 383 | autoescape = settings['autoescape'] 384 | ) 385 | 386 | # 初始化 app 缓存 387 | self.cache = False 388 | cache_cfg = config.get('cache', False) 389 | cache_storage = cache_cfg.get('storage', 'Mongod') 390 | if cache_cfg and hasattr(cache, cache_storage): 391 | Cache = getattr(cache, cache_storage) 392 | self.cache = Cache(**cache_cfg.get('config', {})) 393 | self._sync_key = settings.get('sync_key', 'xcat.web.Application.id') 394 | 395 | ret = super(Application,self).__init__( 396 | [], 397 | default_host, 398 | transforms, 399 | wsgi, 400 | **settings 401 | ) 402 | 403 | route.acl(self) 404 | route.routes(self) 405 | 406 | self.initialize(**settings) 407 | 408 | return ret 409 | 410 | @gen.engine 411 | def sync_ping(self, callback=None): 412 | # 更新同步信号 413 | if self.cache: 414 | self._sync_id = str(uuid.uuid4()) 415 | # 同步 id 416 | yield gen.Task(self.sync, self._sync_id) 417 | if callback: 418 | callback(True) 419 | 420 | @gen.engine 421 | def sync(self, sync_id, callback=None): 422 | route.reset() 423 | 424 | # 重新加载 app handlers 425 | app_handlers = self.settings['app_path'].split(os.path.sep).pop() + '.handlers' 426 | handlers = import_object(app_handlers) 427 | 428 | 429 | for name in handlers.__all__: 430 | handler_module = import_object(app_handlers + '.' + name) 431 | reload(handler_module) 432 | for v in dir(handler_module): 433 | o = getattr(handler_module,v) 434 | if type(o) is types.ModuleType\ 435 | and o.__name__.find('.handlers.') != -1: 436 | reload(o) 437 | 438 | #print route._routes 439 | 440 | route.acl(self) 441 | route.routes(self) 442 | self.initialize(**self.settings) 443 | 444 | # 标记已同步 445 | yield gen.Task(self.cache.set, self._sync_key, sync_id) 446 | 447 | if callback: 448 | callback(True) 449 | 450 | @sync_app 451 | def __call__(self, request): 452 | return super(Application, self).__call__(request) 453 | 454 | @plugins.init 455 | @gen.engine 456 | def initialize(self, **settings): 457 | if self.cache: 458 | self._sync_id = yield gen.Task(self.cache.get, self._sync_key, 0) 459 | 460 | class RequestHandler(RequestHandler): 461 | 462 | # 存放路由 463 | routes = [] 464 | 465 | def finish(self, chunk=None): 466 | super(RequestHandler, self).finish(chunk) 467 | self._on_finish() 468 | 469 | def prepare(self): 470 | if not hasattr(options, ('tforms_locale')): 471 | define('tforms_locale', default=self._) 472 | #options.tforms_locale = self._ 473 | 474 | 475 | @plugins.Events.on_finish 476 | def _on_finish(self): 477 | # 关闭数据库连接 478 | # database = self.settings.get('database') 479 | # if database: 480 | # database.close() 481 | pass 482 | 483 | # 没有权限时的处理 484 | def on_access_denied(self): 485 | self.write_error(403) 486 | 487 | @plugins.Events.on_init 488 | def initialize(self): 489 | # 记录开始时间 490 | self._start_time = time.time() 491 | 492 | # 打开数据库连接 493 | # database = self.settings.get('database') 494 | # if database: 495 | # database.connect() 496 | 497 | 498 | def is_ajax(self): 499 | return "XMLHttpRequest" == self.request.headers.get("X-Requested-With") 500 | 501 | # 多国语言 502 | def _(self, txt, plural_message=None, count=None): 503 | if txt == None: 504 | return txt 505 | return self.locale.translate(unicode(str(txt),'utf8'),plural_message,count) 506 | 507 | @plugins.Events.before_execute 508 | @acl 509 | def _execute(self, transforms, *args, **kwargs): 510 | return super(RequestHandler,self)._execute(transforms, *args, **kwargs) 511 | 512 | @plugins.Events.before_render 513 | def render(self, template_name, **kwargs): 514 | return super(RequestHandler,self).render(template_name, **kwargs) 515 | 516 | def render_string(self, template_name, **kwargs): 517 | context = self.get_template_namespace() 518 | context.update({ 519 | 'json_encode': utils.Json.encode, 520 | 'Date' : arrow , 521 | 'url_for' : route.url_for , 522 | '_' : self._ , 523 | }) 524 | 525 | context.update(self.ui) 526 | context.update(kwargs) 527 | 528 | template = self.application.jinja_env.get_template( 529 | template_name, 530 | parent=self.get_template_path() 531 | ) 532 | return template.render(**context) 533 | 534 | @session 535 | def set_current_user(self,session): 536 | if hasattr(self, 'session'): 537 | self.session['current_user'] = session 538 | 539 | @session 540 | def get_current_user(self): 541 | if hasattr(self, 'session'): 542 | return self.session.get('current_user', {}) 543 | return {} 544 | 545 | def get_error_html(self, status_code = 'tip', **kwargs): 546 | # 检查模板目录是否存在 547 | error_tpl_path = os.path.join(self.settings['template_path'], 'error') 548 | 549 | msg = kwargs.get('msg', False) or kwargs.get('exception') 550 | if not os.path.isdir(error_tpl_path): 551 | return self.write("
%s" % msg) 552 | 553 | tpl_name = '%s.html' % status_code 554 | # 模板不存在 555 | if not os.path.isfile(os.path.join(error_tpl_path, tpl_name)): 556 | if not Validators.is_number(status_code): 557 | return self.write("
%s" % msg) 558 | status_code = str(status_code) 559 | # 40x.html 560 | tpl_name = '%s%sx.html' % (status_code[0], status_code[1]) 561 | if os.path.isfile(os.path.join(error_tpl_path, tpl_name)): 562 | return self.render_string('error/%s' % tpl_name, **kwargs) 563 | 564 | # 4xx.html 565 | tpl_name = '%sxx.html' % status_code[0] 566 | if os.path.isfile(os.path.join(error_tpl_path, tpl_name)): 567 | return self.render_string('error/%s' % tpl_name, **kwargs) 568 | 569 | return self.write("
%s" % msg) 570 | else: 571 | return self.render_string('error/%s.html' % status_code, **kwargs) 572 | 573 | def write_error(self, status_code = 'tip', **kwargs): 574 | if self.is_ajax() and kwargs.get('msg',False) : 575 | return self.write({ 576 | 'success' : False , 577 | 'msg' : (kwargs.get('msg', False) or kwargs.get('exception')) 578 | }) 579 | return super(RequestHandler,self).write_error(status_code, **kwargs) 580 | 581 | # 取运行时间 582 | def get_run_time(self): 583 | return round(time.time() - self._start_time , 3) 584 | 585 | class _404Handler(RequestHandler): 586 | '''404 的处理''' 587 | 588 | def get(self, url): 589 | if hasattr(self,'is_reload'): 590 | return self.redirect(url) 591 | 592 | return self.write_error(404) 593 | 594 | def post(self, url): 595 | return self.get(url) 596 | 597 | --------------------------------------------------------------------------------