最近准备做个简单的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")