最近准备做个简单的web服务器,通过配置url method header 等属性实现自定义的API,框架考虑的是Tornado和PyRestful。然后就遇到了问题,API的数量是动态生成的,每个API的请求方式也不一样。如果单纯的PyRestful是满足不了的。
import pyrestful.rest import tornado.ioloop from pyrestful import mediatypes from pyrestful.rest import get, post class Book(object): isbn = int title = str class BookResource(pyrestful.rest.RestHandler): @get(_path="/books/json/{isbn}", _types=[int], _produces=mediatypes.APPLICATION_JSON) def getBookJSON(self, isbn): book = Book() book.isbn = isbn book.title = "My book for isbn " + str(isbn) return book
因此需要动态生成class及claas的method,还好Python是有对应的语法的:Metaclass
通过继承type,重写__new__(cls, name, bases, attrs)方法
class RestHandleMetaclass(type): def __new__(cls, name, bases, attrs): # do something ,such as redefinition attrs # ps: attr is a tuple return type.__new__(cls, name, (RestHandler,), attrs)
接着遇到了第二个问题,@get(_path="/books/json/{isbn}", _types=[int], _produces=mediatypes.APPLICATION_JSON) 如何添加到方法上面。一开始我以为@符号是注解,走了不少弯路,后来才知道@在Python中是用来处理Decorator(装饰器)的。(参考资料:AB)。 由于attrs中的元素是方法,如何生成方法,同时已经被装饰器处理过,同时能修改@get()中的参数,并最终返回一个方法 成了第三个问题。还好pyrestful.rest.get启发了我
def get(*params, **kwparams): """ Decorator for config a python function like a Rest GET verb """ def method(f): return config(f,'GET',**kwparams) return method
Python中的Method内部是可以定义Method,并返回Method的。同时尝试后发现内部定义的Method是可以加上@get,最终返回装饰器进行处理后的Method
class RestHandleMetaclass(type): @staticmethod def dynamicDecorator(data): _path = "/books/json/" + str(data) + "/{isbn}" _types = [int] _produces = mediatypes.APPLICATION_JSON @get(_path=_path, _types=_types, _produces=_produces) def getBookJSON(self, isbn): book = Book() book.isbn = isbn book.title = "My book for isbn " + str(isbn) return book return getBookJSON def __new__(cls, name, bases, attrs): for x in range(5): attrs["getBookIsbn_" + str(x)] = RestHandleMetaclass.dynamicDecorator(x) return type.__new__(cls, name, (RestHandler,), attrs)
最后补上相关的代码。
class TestA(object, metaclass=RestHandleMetaclass): pass
if __name__ == '__main__': try: print("Start the service") app = pyrestful.rest.RestService([TestA]) app.listen(8080) tornado.ioloop.IOLoop.instance().start() except KeyboardInterrupt: print("\nStop the service")