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

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

## 36 statements 34 run 2 missing 0 excluded 2 partial

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

1 import json

2 import tornado.web

3 import tornado.gen

4 from types import GeneratorType

5 from gramex.transforms import build_transform

6 from gramex.config import app_log, CustomJSONEncoder

7 from .basehandler import BaseHandler

8 from tornado.util import unicode_type

9

10

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