0c058e2bf27daf19e2f5b6503f3c827362ea617a
[pithos] / api / util.py
1 #\r
2 # Copyright (c) 2011 Greek Research and Technology Network\r
3 #\r
4 \r
5 from datetime import timedelta, tzinfo\r
6 from functools import wraps\r
7 from random import choice\r
8 from string import ascii_letters, digits\r
9 from time import time\r
10 from traceback import format_exc\r
11 from wsgiref.handlers import format_date_time\r
12 \r
13 from django.conf import settings\r
14 from django.http import HttpResponse\r
15 from django.template.loader import render_to_string\r
16 from django.utils import simplejson as json\r
17 \r
18 from pithos.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable\r
19 #from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata\r
20 \r
21 import datetime\r
22 import dateutil.parser\r
23 import logging\r
24 \r
25 \r
26 # class UTC(tzinfo):\r
27 #     def utcoffset(self, dt):\r
28 #         return timedelta(0)\r
29 #     \r
30 #     def tzname(self, dt):\r
31 #         return 'UTC'\r
32 #     \r
33 #     def dst(self, dt):\r
34 #         return timedelta(0)\r
35\r
36\r
37 # def isoformat(d):\r
38 #     """Return an ISO8601 date string that includes a timezon."""\r
39 #     \r
40 #     return d.replace(tzinfo=UTC()).isoformat()\r
41\r
42 # def isoparse(s):\r
43 #     """Parse an ISO8601 date string into a datetime object."""\r
44 #     \r
45 #     if not s:\r
46 #         return None\r
47 #     \r
48 #     try:\r
49 #         since = dateutil.parser.parse(s)\r
50 #         utc_since = since.astimezone(UTC()).replace(tzinfo=None)\r
51 #     except ValueError:\r
52 #         raise BadRequest('Invalid changes-since parameter.')\r
53 #     \r
54 #     now = datetime.datetime.now()\r
55 #     if utc_since > now:\r
56 #         raise BadRequest('changes-since value set in the future.')\r
57 #     \r
58 #     if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):\r
59 #         raise BadRequest('Too old changes-since value.')\r
60 #     \r
61 #     return utc_since\r
62 #     \r
63 # def random_password(length=8):\r
64 #     pool = ascii_letters + digits\r
65 #     return ''.join(choice(pool) for i in range(length))\r
66\r
67\r
68 # def get_user():\r
69 #     # XXX Placeholder function, everything belongs to a single SynnefoUser for now\r
70 #     try:\r
71 #         return SynnefoUser.objects.all()[0]\r
72 #     except IndexError:\r
73 #         raise Unauthorized\r
74\r
75 # def get_vm(server_id):\r
76 #     """Return a VirtualMachine instance or raise ItemNotFound."""\r
77 #     \r
78 #     try:\r
79 #         server_id = int(server_id)\r
80 #         return VirtualMachine.objects.get(id=server_id)\r
81 #     except ValueError:\r
82 #         raise BadRequest('Invalid server ID.')\r
83 #     except VirtualMachine.DoesNotExist:\r
84 #         raise ItemNotFound('Server not found.')\r
85\r
86 # def get_vm_meta(server_id, key):\r
87 #     """Return a VirtualMachineMetadata instance or raise ItemNotFound."""\r
88 #     \r
89 #     try:\r
90 #         server_id = int(server_id)\r
91 #         return VirtualMachineMetadata.objects.get(meta_key=key, vm=server_id)\r
92 #     except VirtualMachineMetadata.DoesNotExist:\r
93 #         raise ItemNotFound('Metadata key not found.')\r
94\r
95 # def get_image(image_id):\r
96 #     """Return an Image instance or raise ItemNotFound."""\r
97 #     \r
98 #     try:\r
99 #         image_id = int(image_id)\r
100 #         return Image.objects.get(id=image_id)\r
101 #     except Image.DoesNotExist:\r
102 #         raise ItemNotFound('Image not found.')\r
103\r
104 # def get_image_meta(image_id, key):\r
105 #     """Return a ImageMetadata instance or raise ItemNotFound."""\r
106\r
107 #     try:\r
108 #         image_id = int(image_id)\r
109 #         return ImageMetadata.objects.get(meta_key=key, image=image_id)\r
110 #     except ImageMetadata.DoesNotExist:\r
111 #         raise ItemNotFound('Metadata key not found.')\r
112\r
113\r
114 # def get_request_dict(request):\r
115 #     """Returns data sent by the client as a python dict."""\r
116 #     \r
117 #     data = request.raw_post_data\r
118 #     if request.META.get('CONTENT_TYPE').startswith('application/json'):\r
119 #         try:\r
120 #             return json.loads(data)\r
121 #         except ValueError:\r
122 #             raise BadRequest('Invalid JSON data.')\r
123 #     else:\r
124 #         raise BadRequest('Unsupported Content-Type.')\r
125 \r
126 def update_response_headers(request, response):\r
127     if request.serialization == 'xml':\r
128         response['Content-Type'] = 'application/xml; charset=UTF-8'\r
129     elif request.serialization == 'json':\r
130         response['Content-Type'] = 'application/json; charset=UTF-8'\r
131     else:\r
132         response['Content-Type'] = 'text/plain; charset=UTF-8'\r
133 \r
134     if settings.TEST:\r
135         response['Date'] = format_date_time(time())\r
136 \r
137 # def render_metadata(request, metadata, use_values=False, status=200):\r
138 #     if request.serialization == 'xml':\r
139 #         data = render_to_string('metadata.xml', {'metadata': metadata})\r
140 #     else:\r
141 #         d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}\r
142 #         data = json.dumps(d)\r
143 #     return HttpResponse(data, status=status)\r
144\r
145 # def render_meta(request, meta, status=200):\r
146 #     if request.serialization == 'xml':\r
147 #         data = render_to_string('meta.xml', {'meta': meta})\r
148 #     else:\r
149 #         data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})\r
150 #     return HttpResponse(data, status=status)\r
151 \r
152 def render_fault(request, fault):\r
153     if settings.DEBUG or settings.TEST:\r
154         fault.details = format_exc(fault)\r
155     \r
156 #     if request.serialization == 'xml':\r
157 #         data = render_to_string('fault.xml', {'fault': fault})\r
158 #     else:\r
159 #         d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}\r
160 #         data = json.dumps(d)\r
161     \r
162 #     resp = HttpResponse(data, status=fault.code)\r
163     resp = HttpResponse(status=fault.code)\r
164     update_response_headers(request, resp)\r
165     return resp\r
166 \r
167 def request_serialization(request, format_allowed=False):\r
168     """Return the serialization format requested.\r
169        \r
170        Valid formats are 'text' and 'json', 'xml' if `format_allowed` is True.\r
171     """\r
172     \r
173     if not format_allowed:\r
174         return 'text'\r
175     \r
176     format = request.GET.get('format')\r
177     if format == 'json':\r
178         return 'json'\r
179     elif format == 'xml':\r
180         return 'xml'\r
181     \r
182 #     for item in request.META.get('HTTP_ACCEPT', '').split(','):\r
183 #         accept, sep, rest = item.strip().partition(';')\r
184 #         if accept == 'application/json':\r
185 #             return 'json'\r
186 #         elif accept == 'application/xml':\r
187 #             return 'xml'\r
188     \r
189     return 'text'\r
190 \r
191 def api_method(http_method = None, format_allowed = False):\r
192     """Decorator function for views that implement an API method."""\r
193     \r
194     def decorator(func):\r
195         @wraps(func)\r
196         def wrapper(request, *args, **kwargs):\r
197             try:\r
198                 request.serialization = request_serialization(request, format_allowed)\r
199                 if http_method and request.method != http_method:\r
200                     raise BadRequest('Method not allowed.')\r
201                 \r
202                 resp = func(request, *args, **kwargs)\r
203                 update_response_headers(request, resp)\r
204                 return resp\r
205             \r
206             except Fault, fault:\r
207                 return render_fault(request, fault)\r
208             except BaseException, e:\r
209                 logging.exception('Unexpected error: %s' % e)\r
210                 fault = ServiceUnavailable('Unexpected error')\r
211                 return render_fault(request, fault)\r
212         return wrapper\r
213     return decorator\r