2

I am trying to write a simple (lightweight) RESTful server in Python. I have come across the following code from Google:

import web
import json
from mimerender import mimerender

render_xml = lambda message: '<message>%s</message>'%message
render_json = lambda **args: json.dumps(args)
render_html = lambda message: '<html><body>%s</body></html>'%message
render_txt = lambda message: message

urls = (
    '/(.*)', 'greet'
)
app = web.application(urls, globals())

class greet:
    @mimerender(
        default = 'html',
        html = render_html,
        xml  = render_xml,
        json = render_json,
        txt  = render_txt
    )
    def GET(self, name):
        if not name: 
            name = 'world'
        return {'message': 'Hello, ' + name + '!'}

if __name__ == "__main__":
    app.run()

I am unfamiliar with the syntax used on the line @mimerender. This appears to be a a weird combination of a constructor and a function decorator - however, all uses of decorators I have encountered to date are typically written like this:

def foo():
    pass

def foobar():
    pass

@foo
@pass
def some_other_func():
    pass

What does the @mimerender section of the code mean/do?

unwind
  • 391,730
  • 64
  • 469
  • 606
Homunculus Reticulli
  • 65,167
  • 81
  • 216
  • 341

4 Answers4

6
@expr
def foo(args):
    pass

is equivalent to

def foo(args):
    pass
foo = expr(foo)

expr can be any valid python expression, so what happens here is that mimerender(…) returns a callable object (either by being a constructor or by being a function that returns a callable object). Not much magic around here :)

The above call is thus just

def GET(self, name):
    if not name: 
        name = 'world'
    return {'message': 'Hello, ' + name + '!'}
GET = mimerender(…)(GET)
filmor
  • 30,840
  • 6
  • 50
  • 48
  • 1
    Right. The whole story can be found here: http://stackoverflow.com/questions/739654/understanding-python-decorators – Daren Thomas Jan 11 '12 at 11:12
  • @filmor: Don't you mean GET = mimerender(GET)? - (using your foo example) – Homunculus Reticulli Jan 11 '12 at 11:26
  • No, I don't. That's the whole point of it. As answered by someone else, mimerender is a function that *returns* a decorator. So the expression `mimerender(…)` (where `…` are the parameters given above) *is* the decorator, which means nothing more than that it is a function that takes another function and (preferably) returns a function. So it's like `mimerender : params -> (func -> func)` if you're familiar with the mathematical notation. – filmor Jan 11 '12 at 11:34
1

What's happening here is mimerender is a decorator to GET:

@mimerender
def GET(self, name):

But there are also args needed to be passed to mimerender, so they're done there.

From the docstring of mimerender if you want some extra reading:

def mimerender(default=None, override_arg_idx=None, override_input_key=None, **renderers):
    """
    Usage:
        @mimerender(default='xml', override_arg_idx=-1, override_input_key='format', , <renderers>)
        GET(self, ...) (or POST, etc.)

    The decorated function must return a dict with the objects necessary to
    render the final result to the user. The selected renderer will be called
    with the map contents as keyword arguments.
    If override_arg_idx isn't None, the wrapped function's positional argument
    at that index will be removed and used instead of the Accept header.
    override_input_key works the same way, but with web.input().

    Example:
    class greet:
        @mimerender.mimerender(
            default = 'xml',
            override_arg_idx = -1,
            override_input_key = 'format',
            xhtml   = xhtml_templates.greet,
            html    = xhtml_templates.greet,
            xml     = xml_templates.greet,
            json    = json_render,
            yaml    = json_render,
            txt     = json_render,
        )
        def GET(self, param):
            message = 'Hello, %s!'%param
            return {'message':message}
    """
TyrantWave
  • 4,583
  • 2
  • 22
  • 25
1

When called mimerender will construct and return a decorator function using the arguments supplied. The returned decorator function is then used to decorate the GET method.

MattH
  • 37,273
  • 11
  • 82
  • 84
0

You're potentially being misled by the spacing out of the decorator. Imagine it looked like this:

@mimereader(default=html)
def GET(self, name):

Would that make it any clearer?

To explain, this has nothing to do with a constructor. Instead, what's being done here is that mimereader is a function that returns a decorator, rather than an actual decorator. This function accepts a series of arguments which are then "baked in" to the decorator itself. If you look at the definition of mimereader, you'll see that there is an extra level of nested functions to the normal decorator definition - the outer level defines and returns the decorator itself.

Daniel Roseman
  • 588,541
  • 66
  • 880
  • 895