Statistics
| Branch: | Tag: | Revision:

root / api / emitter.py @ c3e8f508

History | View | Annotate | Download (4.3 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
import re
7
from xml.dom import minidom
8
from django.conf.urls.defaults import url
9
from piston.emitters import Emitter, Mimer
10
from piston.resource import Resource as BaseResource
11

    
12
_accept_re = re.compile(r'([^\s;,]+)(?:[^,]*?;\s*q=(\d*(?:\.\d+)?))?')
13

    
14
def parse_accept_header(value):
15
    """Parse an HTTP Accept header
16

17
    Returns an ordered by quality list of tuples (value, quality)
18
    """
19
    if not value:
20
        return []
21

    
22
    result = []
23
    for match in _accept_re.finditer(value):
24
        quality = match.group(2)
25
        if not quality:
26
            quality = 1
27
        else:
28
            quality = max(min(float(quality), 1), 0)
29
        result.append((match.group(1), quality))
30

    
31
    # sort by quality
32
    result.sort(key=lambda x: x[1])
33

    
34
    return result
35

    
36
class Resource(BaseResource):
37
    def determine_emitter(self, request, *args, **kwargs):
38
        """
39
        Override default emitter policy to account for Accept header
40

41
        emitter_format (.json or .xml suffix in URL) always takes precedence.
42

43
        After that, the Accept header is checked; if both JSON and XML are
44
        equally preferred, use JSON.
45

46
        If none of the two were provided, then use JSON as per the
47
        specification.
48
        """
49

    
50
        em = request.GET.get('format', 'json')
51
        if 'emitter_format' in kwargs and \
52
           kwargs["emitter_format"] is not None:
53
            em = kwargs.pop('emitter_format')
54
        elif 'HTTP_ACCEPT' in request.META:
55
            accepts = parse_accept_header(request.META['HTTP_ACCEPT'])
56
            for content_type, quality in accepts:
57
                if content_type == 'application/json':
58
                    break
59
                elif content_type == 'application/xml':
60
                    em = request.GET.get('format', 'xml')
61
                    break
62

    
63
        return em
64

    
65
class OSXMLEmitter(Emitter):
66
    """
67
    Custom XML Emitter that handles some special stuff needed by the API.
68

69
    Shamelessly stolen^Wborrowed code (sans Piston integration) by OpenStack's
70
    Nova project and hence:
71

72
    Copyright 2010 United States Government as represented by the
73
    Administrator of the National Aeronautics and Space Administration.
74
    Copyright 2010 OpenStack LLC.
75

76
    and licensed under the Apache License, Version 2.0
77
    """
78

    
79
    _metadata = {
80
            "server": [ "id", "imageId", "name", "flavorId", "hostId",
81
                        "status", "progress", "progress" ],
82
            "flavor": [ "id", "name", "ram", "disk" ],
83
            "image": [ "id", "name", "updated", "created", "status",
84
                       "serverId", "progress" ],
85
        }
86

    
87
    def _to_xml_node(self, doc, nodename, data):
88
        """Recursive method to convert data members to XML nodes."""
89
        result = doc.createElement(nodename)
90
        if type(data) is list:
91
            if nodename.endswith('s'):
92
                singular = nodename[:-1]
93
            else:
94
                singular = 'item'
95
            for item in data:
96
                node = self._to_xml_node(doc, singular, item)
97
                result.appendChild(node)
98
        elif type(data) is dict:
99
            attrs = self._metadata.get(nodename, {})
100
            for k, v in data.items():
101
                if k in attrs:
102
                    result.setAttribute(k, str(v))
103
                else:
104
                    node = self._to_xml_node(doc, k, v)
105
                    result.appendChild(node)
106
        else: # atom
107
            node = doc.createTextNode(str(data))
108
            result.appendChild(node)
109
        return result
110

    
111
    def render(self, request):
112
        data = self.construct()
113
        # We expect data to contain a single key which is the XML root.
114
        root_key = data.keys()[0]
115
        doc = minidom.Document()
116
        node = self._to_xml_node(doc, root_key, data[root_key])
117
        return node.toprettyxml(indent='    ')
118

    
119
Emitter.register('xml', OSXMLEmitter, 'application/xml')
120
Mimer.register(lambda *a: None, ('application/xml',))
121

    
122
def url_with_format(regex, *args, **kwargs):
123
    """
124
    An extended url() that adds an .json/.xml suffix to the end to avoid DRY
125
    """
126
    if regex[-1] == '$' and regex[-2] != '\\':
127
        regex = regex[:-1]
128
    regex = regex + r'(\.(?P<emitter_format>json|xml))?$'
129
    return url(regex, *args, **kwargs)