1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
'''
Main server classes
@author: jacekf
'''
from corepost import Response, IRESTResource
from corepost.enums import Http
from corepost.routing import UrlRouter, RequestRouter
from .enums import MediaType
from formencode import FancyValidator, Invalid
from twisted.internet import reactor
from twisted.internet.defer import Deferred
from twisted.web.resource import Resource
from twisted.web.server import Site, NOT_DONE_YET
from zope.interface import implementer
#########################################################
#
# CLASSES
#
#########################################################
@implementer(IRESTResource)
class RESTResource(Resource):
'''
Main resource responsible for routing REST requests to the implementing methods
'''
isLeaf = True
#implements(IRESTResource)
def __init__(self,services=(),schema=None,filters=()):
'''
Constructor
'''
self.services = services
self.__router = RequestRouter(self,schema,filters)
Resource.__init__(self)
def render_GET(self,request):
""" Handles all GET requests """
return self.__renderUrl(request)
def render_POST(self,request):
""" Handles all POST requests"""
return self.__renderUrl(request)
def render_PUT(self,request):
""" Handles all PUT requests"""
return self.__renderUrl(request)
def render_DELETE(self,request):
""" Handles all DELETE requests"""
return self.__renderUrl(request)
def __renderUrl(self,request):
try:
val = self.__router.getResponse(request)
# return can be Deferred or Response
if isinstance(val,Deferred):
val.addCallback(self.__finishRequest,request)
return NOT_DONE_YET
elif isinstance(val,Response):
self.__applyResponse(request, val.code, val.headers)
return val.entity
else:
raise RuntimeError("Unexpected return type from request router %s" % val)
except Exception as ex:
self.__applyResponse(request, 500, None)
return str(ex)
def __finishRequest(self,response,request):
if not request.finished:
self.__applyResponse(request, response.code,response.headers)
request.write(response.entity)
request.finish()
def __applyResponse(self,request,code,headers={"content-type":MediaType.TEXT_PLAIN}):
request.setResponseCode(code)
if headers != None:
for header,value in headers.items():
request.setHeader(header, value)
def run(self,port=8080):
"""Shortcut for running app within Twisted reactor"""
factory = Site(self)
reactor.listenTCP(port, factory) #@UndefinedVariable
reactor.run() #@UndefinedVariable
##################################################################################################
#
# DECORATORS
#
##################################################################################################
def route(url,methods=(Http.GET,),accepts=MediaType.WILDCARD,produces=None,cache=True):
'''
Main decorator for registering REST functions
'''
def decorator(f):
def wrap(*args,**kwargs):
return f
router = UrlRouter(f, url, methods, accepts, produces, cache)
setattr(wrap,'corepostRequestRouter',router)
return wrap
return decorator
def validate(schema=None,**vKwargs):
'''
Main decorator for registering additional validators for incoming URL arguments
'''
def fn(realfn):
def wrap(*args,**kwargs):
# first run schema validation, then the custom validators
errors = []
if schema != None:
try:
schema.to_python(kwargs)
except Invalid as ex:
for arg, error in list(ex.error_dict.items()):
errors.append("%s: %s ('%s')" % (arg,error.msg,error.value))
# custom validators
for arg in list(vKwargs.keys()):
validator = vKwargs[arg]
if arg in kwargs:
val = kwargs[arg]
try:
validator.to_python(val)
except Invalid as ex:
errors.append("%s: %s ('%s')" % (arg,ex,val))
else:
if isinstance(validator,FancyValidator) and validator.not_empty:
raise TypeError("Missing mandatory argument '%s'" % arg)
# fire error if anything failed validation
if len(errors) > 0:
raise TypeError('\n'.join(errors))
# all OK
return realfn(*args,**kwargs)
return wrap
return fn