Revision bebba34f api/util.py

b/api/util.py
1
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
2
#
3
# Copyright (c) 2011 Greek Research and Technology Network
4
#
5

  
6
from datetime import timedelta, tzinfo
7
from functools import wraps
8
from random import choice
9
from string import ascii_letters, digits
10
from time import time
11
from traceback import format_exc
12
from wsgiref.handlers import format_date_time
13

  
14
from django.conf import settings
15
from django.http import HttpResponse
16
from django.template.loader import render_to_string
17
from django.utils import simplejson as json
18

  
19
from pithos.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable
20
#from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata
21

  
22
import datetime
23
import dateutil.parser
24
import logging
25

  
26

  
27
# class UTC(tzinfo):
28
#     def utcoffset(self, dt):
29
#         return timedelta(0)
30
#     
31
#     def tzname(self, dt):
32
#         return 'UTC'
33
#     
34
#     def dst(self, dt):
35
#         return timedelta(0)
36
# 
37
# 
38
# def isoformat(d):
39
#     """Return an ISO8601 date string that includes a timezon."""
40
#     
41
#     return d.replace(tzinfo=UTC()).isoformat()
42
# 
43
# def isoparse(s):
44
#     """Parse an ISO8601 date string into a datetime object."""
45
#     
46
#     if not s:
47
#         return None
48
#     
49
#     try:
50
#         since = dateutil.parser.parse(s)
51
#         utc_since = since.astimezone(UTC()).replace(tzinfo=None)
52
#     except ValueError:
53
#         raise BadRequest('Invalid changes-since parameter.')
54
#     
55
#     now = datetime.datetime.now()
56
#     if utc_since > now:
57
#         raise BadRequest('changes-since value set in the future.')
58
#     
59
#     if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
60
#         raise BadRequest('Too old changes-since value.')
61
#     
62
#     return utc_since
63
#     
64
# def random_password(length=8):
65
#     pool = ascii_letters + digits
66
#     return ''.join(choice(pool) for i in range(length))
67
# 
68
# 
69
# def get_user():
70
#     # XXX Placeholder function, everything belongs to a single SynnefoUser for now
71
#     try:
72
#         return SynnefoUser.objects.all()[0]
73
#     except IndexError:
74
#         raise Unauthorized
75
# 
76
# def get_vm(server_id):
77
#     """Return a VirtualMachine instance or raise ItemNotFound."""
78
#     
79
#     try:
80
#         server_id = int(server_id)
81
#         return VirtualMachine.objects.get(id=server_id)
82
#     except ValueError:
83
#         raise BadRequest('Invalid server ID.')
84
#     except VirtualMachine.DoesNotExist:
85
#         raise ItemNotFound('Server not found.')
86
# 
87
# def get_vm_meta(server_id, key):
88
#     """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
89
#     
90
#     try:
91
#         server_id = int(server_id)
92
#         return VirtualMachineMetadata.objects.get(meta_key=key, vm=server_id)
93
#     except VirtualMachineMetadata.DoesNotExist:
94
#         raise ItemNotFound('Metadata key not found.')
95
# 
96
# def get_image(image_id):
97
#     """Return an Image instance or raise ItemNotFound."""
98
#     
99
#     try:
100
#         image_id = int(image_id)
101
#         return Image.objects.get(id=image_id)
102
#     except Image.DoesNotExist:
103
#         raise ItemNotFound('Image not found.')
104
# 
105
# def get_image_meta(image_id, key):
106
#     """Return a ImageMetadata instance or raise ItemNotFound."""
107
# 
108
#     try:
109
#         image_id = int(image_id)
110
#         return ImageMetadata.objects.get(meta_key=key, image=image_id)
111
#     except ImageMetadata.DoesNotExist:
112
#         raise ItemNotFound('Metadata key not found.')
113
# 
114
# 
115
# def get_request_dict(request):
116
#     """Returns data sent by the client as a python dict."""
117
#     
118
#     data = request.raw_post_data
119
#     if request.META.get('CONTENT_TYPE').startswith('application/json'):
120
#         try:
121
#             return json.loads(data)
122
#         except ValueError:
123
#             raise BadRequest('Invalid JSON data.')
124
#     else:
125
#         raise BadRequest('Unsupported Content-Type.')
126

  
127
def update_response_headers(request, response):
128
    if request.serialization == 'xml':
129
        response['Content-Type'] = 'application/xml; charset=UTF-8'
130
    elif request.serialization == 'json':
131
        response['Content-Type'] = 'application/json; charset=UTF-8'
132
    else:
133
        response['Content-Type'] = 'text/plain; charset=UTF-8'
134

  
135
    if settings.TEST:
136
        response['Date'] = format_date_time(time())
137

  
138
# def render_metadata(request, metadata, use_values=False, status=200):
139
#     if request.serialization == 'xml':
140
#         data = render_to_string('metadata.xml', {'metadata': metadata})
141
#     else:
142
#         d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
143
#         data = json.dumps(d)
144
#     return HttpResponse(data, status=status)
145
# 
146
# def render_meta(request, meta, status=200):
147
#     if request.serialization == 'xml':
148
#         data = render_to_string('meta.xml', {'meta': meta})
149
#     else:
150
#         data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
151
#     return HttpResponse(data, status=status)
152

  
153
def render_fault(request, fault):
154
    if settings.DEBUG or settings.TEST:
155
        fault.details = format_exc(fault)
156
    
157
#     if request.serialization == 'xml':
158
#         data = render_to_string('fault.xml', {'fault': fault})
159
#     else:
160
#         d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
161
#         data = json.dumps(d)
162
    
163
#     resp = HttpResponse(data, status=fault.code)
164
    resp = HttpResponse(status=fault.code)
165
    update_response_headers(request, resp)
166
    return resp
167

  
168
def request_serialization(request, format_allowed=False):
169
    """Return the serialization format requested.
170
       
171
       Valid formats are 'text' and 'json', 'xml' if `format_allowed` is True.
172
    """
173
    
174
    if not format_allowed:
175
        return 'text'
176
    
177
    format = request.GET.get('format')
178
    if format == 'json':
179
        return 'json'
180
    elif format == 'xml':
181
        return 'xml'
182
    
183
#     for item in request.META.get('HTTP_ACCEPT', '').split(','):
184
#         accept, sep, rest = item.strip().partition(';')
185
#         if accept == 'application/json':
186
#             return 'json'
187
#         elif accept == 'application/xml':
188
#             return 'xml'
189
    
190
    return 'text'
191

  
192
def api_method(http_method = None, format_allowed = False):
193
    """Decorator function for views that implement an API method."""
194
    
195
    def decorator(func):
196
        @wraps(func)
197
        def wrapper(request, *args, **kwargs):
198
            try:
199
                request.serialization = request_serialization(request, format_allowed)
200
                if http_method and request.method != http_method:
201
                    raise BadRequest('Method not allowed.')
202
                
203
                resp = func(request, *args, **kwargs)
204
                update_response_headers(request, resp)
205
                return resp
206
            
207
            except Fault, fault:
208
                return render_fault(request, fault)
209
            except BaseException, e:
210
                logging.exception('Unexpected error: %s' % e)
211
                fault = ServiceUnavailable('Unexpected error')
212
                return render_fault(request, fault)
213
        return wrapper
214
    return decorator
1
#
2
# Copyright (c) 2011 Greek Research and Technology Network
3
#
4

  
5
from datetime import timedelta, tzinfo
6
from functools import wraps
7
from random import choice
8
from string import ascii_letters, digits
9
from time import time
10
from traceback import format_exc
11
from wsgiref.handlers import format_date_time
12

  
13
from django.conf import settings
14
from django.http import HttpResponse
15
from django.template.loader import render_to_string
16
from django.utils import simplejson as json
17

  
18
from pithos.api.faults import Fault, BadRequest, ItemNotFound, ServiceUnavailable
19
#from synnefo.db.models import SynnefoUser, Image, ImageMetadata, VirtualMachine, VirtualMachineMetadata
20

  
21
import datetime
22
import dateutil.parser
23
import logging
24

  
25

  
26
# class UTC(tzinfo):
27
#     def utcoffset(self, dt):
28
#         return timedelta(0)
29
#     
30
#     def tzname(self, dt):
31
#         return 'UTC'
32
#     
33
#     def dst(self, dt):
34
#         return timedelta(0)
35
# 
36
# 
37
# def isoformat(d):
38
#     """Return an ISO8601 date string that includes a timezon."""
39
#     
40
#     return d.replace(tzinfo=UTC()).isoformat()
41
# 
42
# def isoparse(s):
43
#     """Parse an ISO8601 date string into a datetime object."""
44
#     
45
#     if not s:
46
#         return None
47
#     
48
#     try:
49
#         since = dateutil.parser.parse(s)
50
#         utc_since = since.astimezone(UTC()).replace(tzinfo=None)
51
#     except ValueError:
52
#         raise BadRequest('Invalid changes-since parameter.')
53
#     
54
#     now = datetime.datetime.now()
55
#     if utc_since > now:
56
#         raise BadRequest('changes-since value set in the future.')
57
#     
58
#     if now - utc_since > timedelta(seconds=settings.POLL_LIMIT):
59
#         raise BadRequest('Too old changes-since value.')
60
#     
61
#     return utc_since
62
#     
63
# def random_password(length=8):
64
#     pool = ascii_letters + digits
65
#     return ''.join(choice(pool) for i in range(length))
66
# 
67
# 
68
# def get_user():
69
#     # XXX Placeholder function, everything belongs to a single SynnefoUser for now
70
#     try:
71
#         return SynnefoUser.objects.all()[0]
72
#     except IndexError:
73
#         raise Unauthorized
74
# 
75
# def get_vm(server_id):
76
#     """Return a VirtualMachine instance or raise ItemNotFound."""
77
#     
78
#     try:
79
#         server_id = int(server_id)
80
#         return VirtualMachine.objects.get(id=server_id)
81
#     except ValueError:
82
#         raise BadRequest('Invalid server ID.')
83
#     except VirtualMachine.DoesNotExist:
84
#         raise ItemNotFound('Server not found.')
85
# 
86
# def get_vm_meta(server_id, key):
87
#     """Return a VirtualMachineMetadata instance or raise ItemNotFound."""
88
#     
89
#     try:
90
#         server_id = int(server_id)
91
#         return VirtualMachineMetadata.objects.get(meta_key=key, vm=server_id)
92
#     except VirtualMachineMetadata.DoesNotExist:
93
#         raise ItemNotFound('Metadata key not found.')
94
# 
95
# def get_image(image_id):
96
#     """Return an Image instance or raise ItemNotFound."""
97
#     
98
#     try:
99
#         image_id = int(image_id)
100
#         return Image.objects.get(id=image_id)
101
#     except Image.DoesNotExist:
102
#         raise ItemNotFound('Image not found.')
103
# 
104
# def get_image_meta(image_id, key):
105
#     """Return a ImageMetadata instance or raise ItemNotFound."""
106
# 
107
#     try:
108
#         image_id = int(image_id)
109
#         return ImageMetadata.objects.get(meta_key=key, image=image_id)
110
#     except ImageMetadata.DoesNotExist:
111
#         raise ItemNotFound('Metadata key not found.')
112
# 
113
# 
114
# def get_request_dict(request):
115
#     """Returns data sent by the client as a python dict."""
116
#     
117
#     data = request.raw_post_data
118
#     if request.META.get('CONTENT_TYPE').startswith('application/json'):
119
#         try:
120
#             return json.loads(data)
121
#         except ValueError:
122
#             raise BadRequest('Invalid JSON data.')
123
#     else:
124
#         raise BadRequest('Unsupported Content-Type.')
125

  
126
def update_response_headers(request, response):
127
    if request.serialization == 'xml':
128
        response['Content-Type'] = 'application/xml; charset=UTF-8'
129
    elif request.serialization == 'json':
130
        response['Content-Type'] = 'application/json; charset=UTF-8'
131
    else:
132
        response['Content-Type'] = 'text/plain; charset=UTF-8'
133

  
134
    if settings.TEST:
135
        response['Date'] = format_date_time(time())
136

  
137
# def render_metadata(request, metadata, use_values=False, status=200):
138
#     if request.serialization == 'xml':
139
#         data = render_to_string('metadata.xml', {'metadata': metadata})
140
#     else:
141
#         d = {'metadata': {'values': metadata}} if use_values else {'metadata': metadata}
142
#         data = json.dumps(d)
143
#     return HttpResponse(data, status=status)
144
# 
145
# def render_meta(request, meta, status=200):
146
#     if request.serialization == 'xml':
147
#         data = render_to_string('meta.xml', {'meta': meta})
148
#     else:
149
#         data = json.dumps({'meta': {meta.meta_key: meta.meta_value}})
150
#     return HttpResponse(data, status=status)
151

  
152
def render_fault(request, fault):
153
    if settings.DEBUG or settings.TEST:
154
        fault.details = format_exc(fault)
155
    
156
#     if request.serialization == 'xml':
157
#         data = render_to_string('fault.xml', {'fault': fault})
158
#     else:
159
#         d = {fault.name: {'code': fault.code, 'message': fault.message, 'details': fault.details}}
160
#         data = json.dumps(d)
161
    
162
#     resp = HttpResponse(data, status=fault.code)
163
    resp = HttpResponse(status=fault.code)
164
    update_response_headers(request, resp)
165
    return resp
166

  
167
def request_serialization(request, format_allowed=False):
168
    """Return the serialization format requested.
169
       
170
       Valid formats are 'text' and 'json', 'xml' if `format_allowed` is True.
171
    """
172
    
173
    if not format_allowed:
174
        return 'text'
175
    
176
    format = request.GET.get('format')
177
    if format == 'json':
178
        return 'json'
179
    elif format == 'xml':
180
        return 'xml'
181
    
182
#     for item in request.META.get('HTTP_ACCEPT', '').split(','):
183
#         accept, sep, rest = item.strip().partition(';')
184
#         if accept == 'application/json':
185
#             return 'json'
186
#         elif accept == 'application/xml':
187
#             return 'xml'
188
    
189
    return 'text'
190

  
191
def api_method(http_method = None, format_allowed = False):
192
    """Decorator function for views that implement an API method."""
193
    
194
    def decorator(func):
195
        @wraps(func)
196
        def wrapper(request, *args, **kwargs):
197
            try:
198
                request.serialization = request_serialization(request, format_allowed)
199
                if http_method and request.method != http_method:
200
                    raise BadRequest('Method not allowed.')
201
                
202
                resp = func(request, *args, **kwargs)
203
                update_response_headers(request, resp)
204
                return resp
205
            
206
            except Fault, fault:
207
                return render_fault(request, fault)
208
            except BaseException, e:
209
                logging.exception('Unexpected error: %s' % e)
210
                fault = ServiceUnavailable('Unexpected error')
211
                return render_fault(request, fault)
212
        return wrapper
213
    return decorator

Also available in: Unified diff