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