root / api / emitter.py @ b9809f7c
History | View | Annotate | Download (3.9 kB)
1 |
# vim: ts=4 sts=4 et ai sw=4 fileencoding=utf-8
|
---|---|
2 |
#
|
3 |
# Copyright © 2010 Greek Research and Technology Network
|
4 |
#
|
5 |
|
6 |
from piston.emitters import Emitter, Mimer |
7 |
from piston.resource import Resource as BaseResource |
8 |
from xml.dom import minidom |
9 |
import re |
10 |
|
11 |
_accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
|
12 |
|
13 |
def parse_accept_header(value): |
14 |
"""Parse an HTTP Accept header
|
15 |
|
16 |
Returns an ordered by quality list of tuples (value, quality)
|
17 |
"""
|
18 |
if not value: |
19 |
return []
|
20 |
|
21 |
result = [] |
22 |
for match in _accept_re.finditer(value): |
23 |
quality = match.group(2)
|
24 |
if not quality: |
25 |
quality = 1
|
26 |
else:
|
27 |
quality = max(min(float(quality), 1), 0) |
28 |
result.append((match.group(1), quality))
|
29 |
|
30 |
# sort by quality
|
31 |
result.sort(key=lambda x: x[1]) |
32 |
|
33 |
return result
|
34 |
|
35 |
class Resource(BaseResource): |
36 |
def determine_emitter(self, request, *args, **kwargs): |
37 |
"""
|
38 |
Override default emitter policy to account for Accept header
|
39 |
|
40 |
emitter_format (.json or .xml suffix in URL) always takes precedence.
|
41 |
|
42 |
After that, the Accept header is checked; if both JSON and XML are
|
43 |
equally preferred, use JSON.
|
44 |
|
45 |
If none of the two were provided, then use JSON as per the
|
46 |
specification.
|
47 |
"""
|
48 |
|
49 |
em = request.GET.get('format', 'json') |
50 |
if 'emitter_format' in kwargs: |
51 |
em = kwargs.pop('emitter_format')
|
52 |
elif 'HTTP_ACCEPT' in request.META: |
53 |
accepts = parse_accept_header(request.META['HTTP_ACCEPT'])
|
54 |
for content_type, quality in accepts: |
55 |
if content_type == 'application/json': |
56 |
break
|
57 |
elif content_type == 'application/xml': |
58 |
em = request.GET.get('format', 'xml') |
59 |
break
|
60 |
|
61 |
return em
|
62 |
|
63 |
class OSXMLEmitter(Emitter): |
64 |
"""
|
65 |
Custom XML Emitter that handles some special stuff needed by the API.
|
66 |
|
67 |
Shamelessly stolen^Wborrowed code (sans Piston integration) by OpenStack's
|
68 |
Nova project and hence:
|
69 |
|
70 |
Copyright 2010 United States Government as represented by the
|
71 |
Administrator of the National Aeronautics and Space Administration.
|
72 |
Copyright 2010 OpenStack LLC.
|
73 |
|
74 |
and licensed under the Apache License, Version 2.0
|
75 |
"""
|
76 |
|
77 |
_metadata = { |
78 |
"server": [ "id", "imageId", "name", "flavorId", "hostId", |
79 |
"status", "progress", "progress" ], |
80 |
"flavor": [ "id", "name", "ram", "disk" ], |
81 |
"image": [ "id", "name", "updated", "created", "status", |
82 |
"serverId", "progress" ], |
83 |
} |
84 |
|
85 |
def _to_xml_node(self, doc, nodename, data): |
86 |
"""Recursive method to convert data members to XML nodes."""
|
87 |
result = doc.createElement(nodename) |
88 |
if type(data) is list: |
89 |
if nodename.endswith('s'): |
90 |
singular = nodename[:-1]
|
91 |
else:
|
92 |
singular = 'item'
|
93 |
for item in data: |
94 |
node = self._to_xml_node(doc, singular, item)
|
95 |
result.appendChild(node) |
96 |
elif type(data) is dict: |
97 |
attrs = self._metadata.get(nodename, {})
|
98 |
for k, v in data.items(): |
99 |
if k in attrs: |
100 |
result.setAttribute(k, str(v))
|
101 |
else:
|
102 |
node = self._to_xml_node(doc, k, v)
|
103 |
result.appendChild(node) |
104 |
else: # atom |
105 |
node = doc.createTextNode(str(data))
|
106 |
result.appendChild(node) |
107 |
return result
|
108 |
|
109 |
def render(self, request): |
110 |
data = self.construct()
|
111 |
# We expect data to contain a single key which is the XML root.
|
112 |
root_key = data.keys()[0]
|
113 |
doc = minidom.Document() |
114 |
node = self._to_xml_node(doc, root_key, data[root_key])
|
115 |
return node.toprettyxml(indent=' ') |
116 |
|
117 |
Emitter.register('xml', OSXMLEmitter, 'application/xml') |
118 |
Mimer.register(lambda *a: None, ('application/xml',)) |