1import os.path 2import logging 3 4from wsme.utils import OrderedDict 5from wsme.protocol import CallContext, Protocol, media_type_accept 6 7import wsme.rest 8from wsme.rest import json 9from wsme.rest import xml 10import wsme.runtime 11 12log = logging.getLogger(__name__) 13 14 15class RestProtocol(Protocol): 16 name = 'rest' 17 displayname = 'REST' 18 formatters = { 19 'json': json, 20 'xml': xml, 21 } 22 23 def __init__(self, dataformats=None): 24 if dataformats is None: 25 dataformats = ('json', 'xml') 26 27 self.dataformats = OrderedDict() 28 self.content_types = [] 29 30 for dataformat in dataformats: 31 formatter = self.formatters[dataformat] 32 self.dataformats[dataformat] = formatter 33 self.content_types.extend(formatter.accept_content_types) 34 35 def accept(self, request): 36 for dataformat in self.dataformats: 37 if request.path.endswith('.' + dataformat): 38 return True 39 return media_type_accept(request, self.content_types) 40 41 def iter_calls(self, request): 42 context = CallContext(request) 43 context.outformat = None 44 ext = os.path.splitext(request.path.split('/')[-1])[1] 45 inmime = request.content_type 46 try: 47 offers = request.accept.acceptable_offers(self.content_types) 48 outmime = offers[0][0] 49 except IndexError: 50 outmime = None 51 52 outformat = None 53 informat = None 54 for dfname, df in self.dataformats.items(): 55 if ext == '.' + dfname: 56 outformat = df 57 if not inmime: 58 informat = df 59 60 if outformat is None and request.accept: 61 for dfname, df in self.dataformats.items(): 62 if outmime in df.accept_content_types: 63 outformat = df 64 if not inmime: 65 informat = df 66 67 if outformat is None: 68 for dfname, df in self.dataformats.items(): 69 if inmime == df.content_type: 70 outformat = df 71 72 context.outformat = outformat 73 context.outformat_options = { 74 'nest_result': getattr(self, 'nest_result', False) 75 } 76 if not inmime and informat: 77 inmime = informat.content_type 78 log.debug("Inferred input type: %s" % inmime) 79 context.inmime = inmime 80 yield context 81 82 def extract_path(self, context): 83 path = context.request.path 84 assert path.startswith(self.root._webpath) 85 path = path[len(self.root._webpath):] 86 path = path.strip('/').split('/') 87 88 for dataformat in self.dataformats: 89 if path[-1].endswith('.' + dataformat): 90 path[-1] = path[-1][:-len(dataformat) - 1] 91 92 # Check if the path is actually a function, and if not 93 # see if the http method make a difference 94 # TODO Re-think the function lookup phases. Here we are 95 # doing the job that will be done in a later phase, which 96 # is sub-optimal 97 for p, fdef in self.root.getapi(): 98 if p == path: 99 return path 100 101 # No function at this path. Now check for function that have 102 # this path as a prefix, and declared an http method 103 for p, fdef in self.root.getapi(): 104 if len(p) == len(path) + 1 and p[:len(path)] == path and \ 105 fdef.extra_options.get('method') == context.request.method: 106 return p 107 108 return path 109 110 def read_arguments(self, context): 111 request = context.request 112 funcdef = context.funcdef 113 114 body = None 115 if request.content_length not in (None, 0, '0'): 116 body = request.body 117 if not body and '__body__' in request.params: 118 body = request.params['__body__'] 119 120 args, kwargs = wsme.rest.args.combine_args( 121 funcdef, 122 (wsme.rest.args.args_from_params(funcdef, request.params), 123 wsme.rest.args.args_from_body(funcdef, body, context.inmime)) 124 ) 125 wsme.runtime.check_arguments(funcdef, args, kwargs) 126 return kwargs 127 128 def encode_result(self, context, result): 129 out = context.outformat.encode_result( 130 result, context.funcdef.return_type, 131 **context.outformat_options 132 ) 133 return out 134 135 def encode_error(self, context, errordetail): 136 out = context.outformat.encode_error( 137 context, errordetail 138 ) 139 return out 140