Statistics
| Branch: | Tag: | Revision:

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',))