mirror of https://github.com/mahmoud/boltons.git
166 lines
5.5 KiB
Python
166 lines
5.5 KiB
Python
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()
|