File: table_html_app.py

package info (click to toggle)
python-boltons 25.0.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid, trixie
  • size: 1,236 kB
  • sloc: python: 12,133; makefile: 159; sh: 7
file content (165 lines) | stat: -rw-r--r-- 5,605 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
import os
import json

import clastic
from clastic import Application
from clastic.render import JSONRender
from clastic.middleware import GetParamMiddleware
from clastic import Response
from clastic.sinter import getargspec

from boltons.tableutils import Table

_DATA = json.load(open('meta_stats.json'))

_CUR_PATH = os.path.dirname(os.path.abspath(clastic.__file__))
_CA_PATH = _CUR_PATH + '/_clastic_assets'
_CSS_PATH = _CA_PATH + '/common.css'
_STYLE = open(_CSS_PATH).read()


def fetch_json(url):
    import urllib2
    response = urllib2.urlopen(url)
    content = response.read()
    data = json.loads(content)
    return data


class AutoTableRenderer:
    _html_doctype = '<!doctype html>'
    _html_wrapper, _html_wrapper_close = '<html>', '</html>'
    _html_table_tag = '<table class="clastic-atr-table">'
    _html_style_content = _STYLE

    def __init__(self, max_depth=4, orientation='auto'):
        self.max_depth = max_depth
        self.orientation = orientation

    def _html_format_ep(self, route):
        # TODO: callable object endpoints?
        module_name = route.endpoint.__module__
        try:
            func_name = route.endpoint.func_name
        except:
            func_name = repr(route.endpoint)
        args, _, _, _ = getargspec(route.endpoint)
        argstr = ', '.join(args)
        title = ('<h2><small><sub>%s</sub></small><br/>%s(%s)</h2>'
                 % (module_name, func_name, argstr))
        return title

    def __call__(self, context, _route):
        content_parts = [self._html_wrapper]
        if self._html_style_content:
            content_parts.extend(['<head><style type="text/css">',
                                  self._html_style_content,
                                  '</style></head>'])
        content_parts.append('<body>')
        title = self._html_format_ep(_route)
        content_parts.append(title)
        table = Table.from_data(context, max_depth=self.max_depth)
        table._html_table_tag = self._html_table_tag
        content = table.to_html(max_depth=self.max_depth,
                                orientation=self.orientation)
        content_parts.append(content)
        content_parts.append('</body>')
        content_parts.append(self._html_wrapper_close)
        return Response('\n'.join(content_parts), mimetype='text/html')


class BasicRender:
    _default_mime = 'application/json'
    _format_mime_map = {'html': 'text/html',
                        'json': 'application/json'}

    def __init__(self, dev_mode=True, qp_name='format'):
        self.qp_name = qp_name
        self.json_render = JSONRender(dev_mode=dev_mode)
        self.autotable_render = AutoTableRenderer()

    def render_response(self, request, context, _route):
        from collections.abc import Sized
        if isinstance(context, str):  # already serialized
            if self._guess_json(context):
                return Response(context, mimetype="application/json")
            elif '<html' in context[:168]:
                # based on the longest DOCTYPE I found in a brief search
                return Response(context, mimetype="text/html")
            else:
                return Response(context, mimetype="text/plain")

        # not serialized yet, time to guess what the requester wants
        if not isinstance(context, Sized):
            return Response(str(context), mimetype="text/plain")
        return self._serialize_to_resp(context, request, _route)

    __call__ = render_response

    def _serialize_to_resp(self, context, request, _route):
        req_format = request.args.get(self.qp_name)  # explicit GET query param
        if req_format and req_format not in self._format_mime_map:
            # TODO: badrequest
            raise ValueError('format expected one of %r, not %r'
                             % (self.formats, req_format))

        resp_mime = self._format_mime_map.get(req_format)
        if not resp_mime and request.accept_mimetypes:
            resp_mime = request.accept_mimetypes.best_match(self.mimetypes)
        if resp_mime not in self._mime_format_map:
            resp_mime = self._default_mime

        if resp_mime == 'application/json':
            return self.json_render(context)
        elif resp_mime == 'text/html':
            return self.autotable_render(context, _route)
        return Response(str(context), mimetype="text/plain")

    @property
    def _mime_format_map(self):
        return {v: k for k, v in self._format_mime_map.items()}

    @property
    def formats(self):
        return self._format_mime_map.keys()

    @property
    def mimetypes(self):
        return self._format_mime_map.values()

    @staticmethod
    def _guess_json(text):
        if not text:
            return False
        elif text[0] == '{' and text[-1] == '}':
            return True
        elif text[0] == '[' and text[-1] == ']':
            return True
        else:
            return False

    @classmethod
    def factory(cls, *a, **kw):
        def basic_render_factory(render_arg):
            # behavior doesn't change depending on render_arg
            return cls(*a, **kw)
        return basic_render_factory


def ident_ep(data):
    return data


def main():
    rsc = {'data': _DATA}
    gpm = GetParamMiddleware('url')
    atr = AutoTableRenderer(max_depth=5)
    render_basic = BasicRender()
    app = Application([('/', ident_ep, render_basic),
                       ('/json', ident_ep, render_basic),
                       ('/fetch', fetch_json, render_basic)], rsc, [gpm])
    app.serve()


if __name__ == '__main__':
    main()