ext_json.py 5.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214
  1. """
  2. The JSON Extension adds the :class:`JsonOutputHandler` to render
  3. output in pure JSON, as well as the :class:`JsonConfigHandler` that allows
  4. applications to use JSON configuration files as a drop-in replacement of
  5. the default :class:`cement.ext.ext_configparser.ConfigParserConfigHandler`.
  6. Requirements
  7. ------------
  8. * No external dependencies.
  9. Configuration
  10. -------------
  11. This extension does not honor any application configuration settings.
  12. Usage
  13. _____
  14. **myapp.conf**
  15. .. code-block:: json
  16. {
  17. "myapp": {
  18. "foo": "bar"
  19. }
  20. }
  21. **myapp.py**
  22. .. code-block:: python
  23. from cement.core.foundation import CementApp
  24. class MyApp(CementApp):
  25. class Meta:
  26. label = 'myapp'
  27. extensions = ['json']
  28. config_handler = 'json'
  29. # you probably don't want this to be json by default.. but you can
  30. # output_handler = 'json'
  31. with MyApp() as app:
  32. app.run()
  33. # create some data
  34. data = dict(foo=app.config.get('myapp', 'foo'))
  35. app.render(data)
  36. In general, you likely would not set ``output_handler`` to ``json``, but
  37. rather another type of output handler that display readable output to the
  38. end-user (i.e. Mustache, Genshi, or Tabulate). By default Cement
  39. adds the ``-o`` command line option to allow the end user to override the
  40. output handler. For example: passing ``-o json`` will override the default
  41. output handler and set it to ``JsonOutputHandler``.
  42. See ``CementApp.Meta.handler_override_options``.
  43. .. code-block:: console
  44. $ python myapp.py -o json
  45. {"foo": "bar"}
  46. """
  47. import sys
  48. import json
  49. from ..core import output, backend
  50. from ..utils.misc import minimal_logger
  51. from ..ext.ext_configparser import ConfigParserConfigHandler
  52. LOG = minimal_logger(__name__)
  53. def suppress_output_before_run(app):
  54. """
  55. This is a ``post_argument_parsing`` hook that suppresses console output if
  56. the ``JsonOutputHandler`` is triggered via command line.
  57. :param app: The application object.
  58. """
  59. if not hasattr(app.pargs, 'output_handler_override'):
  60. return
  61. elif app.pargs.output_handler_override == 'json':
  62. app._suppress_output()
  63. def unsuppress_output_before_render(app, data):
  64. """
  65. This is a ``pre_render`` that unsuppresses console output if
  66. the ``JsonOutputHandler`` is triggered via command line so that the JSON
  67. is the only thing in the output.
  68. :param app: The application object.
  69. """
  70. if not hasattr(app.pargs, 'output_handler_override'):
  71. return
  72. elif app.pargs.output_handler_override == 'json':
  73. app._unsuppress_output()
  74. def suppress_output_after_render(app, out_text):
  75. """
  76. This is a ``post_render`` hook that suppresses console output again after
  77. rendering, only if the ``JsonOutputHandler`` is triggered via command
  78. line.
  79. :param app: The application object.
  80. """
  81. if not hasattr(app.pargs, 'output_handler_override'):
  82. return
  83. elif app.pargs.output_handler_override == 'json':
  84. app._suppress_output()
  85. class JsonOutputHandler(output.CementOutputHandler):
  86. """
  87. This class implements the :ref:`IOutput <cement.core.output>`
  88. interface. It provides JSON output from a data dictionary using the
  89. `json <http://docs.python.org/library/json.html>`_ module of the standard
  90. library. Please see the developer documentation on
  91. :ref:`Output Handling <dev_output_handling>`.
  92. This handler forces Cement to suppress console output until
  93. ``app.render`` is called (keeping the output pure JSON). If
  94. troubleshooting issues, you will need to pass the ``--debug`` option in
  95. order to unsuppress output and see what's happening.
  96. """
  97. class Meta:
  98. """Handler meta-data"""
  99. interface = output.IOutput
  100. """The interface this class implements."""
  101. label = 'json'
  102. """The string identifier of this handler."""
  103. #: Whether or not to include ``json`` as an available to choice
  104. #: to override the ``output_handler`` via command line options.
  105. overridable = True
  106. def __init__(self, *args, **kw):
  107. super(JsonOutputHandler, self).__init__(*args, **kw)
  108. def render(self, data_dict, **kw):
  109. """
  110. Take a data dictionary and render it as Json output. Note that the
  111. template option is received here per the interface, however this
  112. handler just ignores it.
  113. :param data_dict: The data dictionary to render.
  114. :param template: This option is completely ignored.
  115. :returns: A JSON encoded string.
  116. :rtype: ``str``
  117. """
  118. LOG.debug("rendering output as Json via %s" % self.__module__)
  119. return json.dumps(data_dict)
  120. class JsonConfigHandler(ConfigParserConfigHandler):
  121. """
  122. This class implements the :ref:`IConfig <cement.core.config>`
  123. interface, and provides the same functionality of
  124. :ref:`ConfigParserConfigHandler <cement.ext.ext_configparser>`
  125. but with JSON configuration files.
  126. """
  127. class Meta:
  128. """Handler meta-data."""
  129. label = 'json'
  130. def __init__(self, *args, **kw):
  131. super(JsonConfigHandler, self).__init__(*args, **kw)
  132. def _parse_file(self, file_path):
  133. """
  134. Parse JSON configuration file settings from file_path, overwriting
  135. existing config settings. If the file does not exist, returns False.
  136. :param file_path: The file system path to the JSON configuration file.
  137. :returns: boolean
  138. """
  139. self.merge(json.load(open(file_path)))
  140. # FIX ME: Should check that file was read properly, however if not it
  141. # will likely raise an exception anyhow.
  142. return True
  143. def load(app):
  144. app.hook.register('post_argument_parsing', suppress_output_before_run)
  145. app.hook.register('pre_render', unsuppress_output_before_render)
  146. app.hook.register('post_render', suppress_output_after_render)
  147. app.handler.register(JsonOutputHandler)
  148. app.handler.register(JsonConfigHandler)