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()
|