Personal wiki notebook (not under development)

Expose.py 4.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118
  1. import cherrypy
  2. import types
  3. # module-level variable that, when set to a view, overrides the view for all exposed methods. used
  4. # by unit tests
  5. view_override = None
  6. def expose( view = None, rss = None ):
  7. """
  8. expose() can be used to tag a method as available for publishing to the web via CherryPy. In
  9. other words, methods that are not exposed cannot be accessed from the web.
  10. The expose() method itself is evaluated where it is used as a decorator, which just puts the view
  11. variable into the enclosing scope of the decorate() function and returns decorate() to be used as
  12. the actual decorator.
  13. Example usage:
  14. @expose( view = Json )
  15. def method(): pass
  16. """
  17. def decorate( function ):
  18. """
  19. When the method being decorated is invoked, its decorator gets invoked instead and is supposed
  20. to return a new function to use in place of the method being decorated (or a modified version
  21. of that function). In this case, the decorator is our decorate() function, and the function it
  22. returns is the render() function. decorate()'s first argument is the method being decorated.
  23. """
  24. def render( *args, **kwargs ):
  25. """
  26. render() pretends that it's the method being decorated. It takes the same arguments and then
  27. invokes the actual method being decorated, passing in those arguments.
  28. With whatever result it gets from calling that method, render() invokes the view from the
  29. outer scope to try to render it. It then results that rendered result.
  30. """
  31. result = {}
  32. # if rss was requested, and this method was exposed for rss, then use rss as the view
  33. if u"rss" in kwargs:
  34. del kwargs[ u"rss" ]
  35. use_rss = True
  36. else:
  37. use_rss = False
  38. # kwarg names must be of type str, not unicode
  39. kwargs = dict( [ ( str( key ), value ) for ( key, value ) in kwargs.items() ] )
  40. # try executing the exposed function
  41. original_error = None
  42. try:
  43. result = function( *args, **kwargs )
  44. except cherrypy.NotFound:
  45. raise
  46. except Exception, error:
  47. original_error = error
  48. if hasattr( error, "to_dict" ):
  49. if not view: raise error
  50. result = error.to_dict()
  51. elif isinstance( error, cherrypy.HTTPRedirect ):
  52. raise
  53. else:
  54. import traceback
  55. traceback.print_exc()
  56. cherrypy.root.report_traceback()
  57. result = dict( error = u"An error occurred when processing your request. Please try again or contact support." )
  58. # if the result is a generator or a string, it's streaming data or just data, so just let CherryPy handle it
  59. if isinstance( result, ( types.GeneratorType, basestring ) ):
  60. return result
  61. redirect = result.get( u"redirect" )
  62. encoding = result.get( u"manual_encode" )
  63. if encoding:
  64. del( result[ u"manual_encode" ] )
  65. def render( view, result, encoding = None ):
  66. output = unicode( view( **result ) )
  67. if not encoding:
  68. return output
  69. return output.encode( encoding )
  70. # try using the supplied view to render the result
  71. try:
  72. if view_override is not None:
  73. return render( view_override, result, encoding )
  74. elif rss and use_rss:
  75. cherrypy.response.headers[ u"Content-Type" ] = u"application/xml"
  76. return render( rss, result, encoding or "utf8" )
  77. elif view:
  78. return render( view, result, encoding )
  79. elif result.get( "view" ):
  80. result_view = result.get( "view" )
  81. del( result[ "view" ] )
  82. return render( result_view, result, encoding )
  83. except:
  84. if redirect is None:
  85. if original_error:
  86. raise original_error
  87. else:
  88. raise
  89. # if that doesn't work, and there's a redirect, then redirect
  90. del( result[ u"redirect" ] )
  91. from urllib import urlencode
  92. if result == {}:
  93. raise cherrypy.HTTPRedirect( u"%s" % redirect )
  94. else:
  95. url_args = urlencode( result )
  96. raise cherrypy.HTTPRedirect( u"%s?%s" % ( redirect, url_args ) )
  97. render.exposed = True
  98. return render
  99. return decorate