Hide keyboard shortcuts

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 

9 

10 

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: 

15 

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) 

41 

42 @tornado.gen.coroutine 

43 def _get(self, *path_args): 

44 if self.redirects: 

45 self.save_redirect_page() 

46 

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) 

52 

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 

56 

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

70 

71 if self.redirects: 

72 self.redirect_next()