Clean up, sort out logging.
[pithos] / pithos / api / util.py
1 #\r
2 # Copyright (c) 2011 Greek Research and Technology Network\r
3 #\r
4 \r
5 from functools import wraps\r
6 \r
7 from time import time\r
8 from wsgiref.handlers import format_date_time\r
9 \r
10 from django.conf import settings\r
11 from django.http import HttpResponse\r
12 \r
13 from pithos.api.faults import Fault, BadRequest, ServiceUnavailable\r
14 \r
15 import datetime\r
16 import logging\r
17 \r
18 def format_meta_key(k):\r
19     """\r
20     Convert underscores to dashes and capitalize intra-dash strings.\r
21     """\r
22     return '-'.join([x.capitalize() for x in k.replace('_', '-').split('-')])\r
23 \r
24 def get_meta(request, prefix):\r
25     """\r
26     Get all prefix-* request headers in a dict. Reformat keys with format_meta_key().\r
27     """\r
28     prefix = 'HTTP_' + prefix.upper().replace('-', '_')\r
29     return dict([(format_meta_key(k[5:]), v) for k, v in request.META.iteritems() if k.startswith(prefix)])\r
30 \r
31 def get_range(request):\r
32     """\r
33     Parse a Range header from the request.\r
34     Either returns None, or an (offset, length) tuple.\r
35     If no offset is defined offset equals 0.\r
36     If no length is defined length is None.\r
37     """\r
38     \r
39     range = request.GET.get('range')\r
40     if not range:\r
41         return None\r
42     \r
43     range = range.replace(' ', '')\r
44     if not range.startswith('bytes='):\r
45         return None\r
46     \r
47     parts = range.split('-')\r
48     if len(parts) != 2:\r
49         return None\r
50     \r
51     offset, length = parts\r
52     if offset == '' and length == '':\r
53         return None\r
54     \r
55     if offset != '':\r
56         try:\r
57             offset = int(offset)\r
58         except ValueError:\r
59             return None\r
60     else:\r
61         offset = 0\r
62     \r
63     if length != '':\r
64         try:\r
65             length = int(length)\r
66         except ValueError:\r
67             return None\r
68     else:\r
69         length = None\r
70     \r
71     return (offset, length)\r
72 \r
73 def update_response_headers(request, response):\r
74     if request.serialization == 'xml':\r
75         response['Content-Type'] = 'application/xml; charset=UTF-8'\r
76     elif request.serialization == 'json':\r
77         response['Content-Type'] = 'application/json; charset=UTF-8'\r
78     else:\r
79         response['Content-Type'] = 'text/plain; charset=UTF-8'\r
80 \r
81     if settings.TEST:\r
82         response['Date'] = format_date_time(time())\r
83 \r
84 def render_fault(request, fault):\r
85     response = HttpResponse(status = fault.code)\r
86     update_response_headers(request, response)\r
87     return response\r
88 \r
89 def request_serialization(request, format_allowed=False):\r
90     """\r
91     Return the serialization format requested.\r
92        \r
93     Valid formats are 'text' and 'json', 'xml' if `format_allowed` is True.\r
94     """\r
95     \r
96     if not format_allowed:\r
97         return 'text'\r
98     \r
99     format = request.GET.get('format')\r
100     if format == 'json':\r
101         return 'json'\r
102     elif format == 'xml':\r
103         return 'xml'\r
104     \r
105     for item in request.META.get('HTTP_ACCEPT', '').split(','):\r
106         accept, sep, rest = item.strip().partition(';')\r
107         if accept == 'text/plain':\r
108             return 'text'\r
109         elif accept == 'application/json':\r
110             return 'json'\r
111         elif accept == 'application/xml' or accept == 'text/xml':\r
112             return 'xml'\r
113     \r
114     return 'text'\r
115 \r
116 def api_method(http_method = None, format_allowed = False):\r
117     """\r
118     Decorator function for views that implement an API method.\r
119     """\r
120     \r
121     def decorator(func):\r
122         @wraps(func)\r
123         def wrapper(request, *args, **kwargs):\r
124             try:\r
125                 if http_method and request.method != http_method:\r
126                     raise BadRequest('Method not allowed.')\r
127 \r
128                 # The args variable may contain up to (account, container, object).\r
129                 if len(args) > 1 and len(args[1]) > 256:\r
130                     raise BadRequest('Container name too large.')\r
131                 if len(args) > 2 and len(args[2]) > 1024:\r
132                     raise BadRequest('Object name too large.')\r
133                 \r
134                 # Fill in custom request variables.\r
135                 request.serialization = request_serialization(request, format_allowed)\r
136                 # TODO: Authenticate.\r
137                 request.user = "test"\r
138                 \r
139                 response = func(request, *args, **kwargs)\r
140                 update_response_headers(request, response)\r
141                 return response\r
142             except Fault, fault:\r
143                 return render_fault(request, fault)\r
144             except BaseException, e:\r
145                 logging.exception('Unexpected error: %s' % e)\r
146                 fault = ServiceUnavailable('Unexpected error')\r
147                 return render_fault(request, fault)\r
148         return wrapper\r
149     return decorator\r