Coverage for gramex\handlers\functionhandler.py : 93%

Hot-keys on this page
r m x p toggle line displays
j k next/prev highlighted chunk
0 (zero) top of page
1 (one) first highlighted chunk
1import json
2import tornado.web
3import tornado.gen
4from types import GeneratorType
5from gramex.transforms import build_transform
6from gramex.config import app_log, CustomJSONEncoder
7from .basehandler import BaseHandler
8from tornado.util import unicode_type
11class FunctionHandler(BaseHandler):
12 '''
13 Renders the output of a function when the URL is called via GET or POST. It
14 accepts these parameters when initialized:
16 :arg string function: a string that resolves into any Python function or
17 method (e.g. ``str.lower``). By default, it is called as
18 ``function(handler)`` where handler is this RequestHandler, but you can
19 override ``args`` and ``kwargs`` below to replace it with other
20 parameters. The result is rendered as-is (and hence must be a string, or
21 a Future that resolves to a string.) You can also yield one or more
22 results. These are written immediately, in order.
23 :arg list args: positional arguments to be passed to the function.
24 :arg dict kwargs: keyword arguments to be passed to the function.
25 :arg dict headers: HTTP headers to set on the response.
26 :arg list methods: List of HTTP methods to allow. Defaults to
27 `['GET', 'POST']`.
28 :arg string redirect: URL to redirect to when the result is done. Used to
29 trigger calculations without displaying any output.
30 '''
31 @classmethod
32 def setup(cls, headers={}, methods=['GET', 'POST'], **kwargs):
33 super(FunctionHandler, cls).setup(**kwargs)
34 # Don't use cls.info.function = build_transform(...) -- Python treats it as a method
35 cls.info = {}
36 cls.info['function'] = build_transform(kwargs, vars={'handler': None},
37 filename='url: %s' % cls.name)
38 cls.headers = headers
39 for method in (methods if isinstance(methods, (tuple, list)) else [methods]):
40 setattr(cls, method.lower(), cls._get)
42 @tornado.gen.coroutine
43 def _get(self, *path_args):
44 if self.redirects:
45 self.save_redirect_page()
47 if 'function' not in self.info: 47 ↛ 48line 47 didn't jump to line 48, because the condition on line 47 was never true
48 raise ValueError('Invalid function definition in url:%s' % self.name)
49 result = self.info['function'](handler=self)
50 for header_name, header_value in self.headers.items():
51 self.set_header(header_name, header_value)
53 # Use multipart to check if the respose has multiple parts. Don't
54 # flush unless it's multipart. Flushing disables Etag
55 multipart = isinstance(result, GeneratorType) or len(result) > 1
57 # build_transform results are iterable. Loop through each item
58 for item in result:
59 # Resolve futures and write the result immediately
60 if tornado.concurrent.is_future(item):
61 item = yield item
62 if isinstance(item, (bytes, unicode_type, dict)): 62 ↛ 68line 62 didn't jump to line 68, because the condition on line 62 was never false
63 self.write(json.dumps(item, separators=(',', ':'), ensure_ascii=True,
64 cls=CustomJSONEncoder) if isinstance(item, dict) else item)
65 if multipart:
66 self.flush()
67 else:
68 app_log.warning('url:%s: FunctionHandler can write strings/dict, not %s',
69 self.name, repr(item))
71 if self.redirects:
72 self.redirect_next()