Enrico Zini: Custom function decorators with TurboGears 2
Custom function decorators with TurboGears 2
I am exposing some library functions using a TurboGears2 controller (see
web-api-with-turbogears2). It turns out that some functions return a dict,
some a list, some a string, and TurboGears 2 only allows JSON serialisation for
dicts.
A simple work-around for this is to wrap the function result into a dict,
something like this:
@expose("json") @validate(validator_dispatcher, error_handler=api_validation_error) def list_colours(self, filter=None, productID=None, maxResults=100, **kw): # Call API res = self.engine.list_colours(filter, productID, maxResults) # Return result return dict(r=res)It would be nice, however, to have an
@webapi() decorator that
automatically wraps the function result with the dict:
def webapi(func): def dict_wrap(*args, **kw): return dict(r=func(*args, **kw)) return dict_wrap # ...in the controller... @expose("json") @validate(validator_dispatcher, error_handler=api_validation_error) @webapi def list_colours(self, filter=None, productID=None, maxResults=100, **kw): # Call API res = self.engine.list_colours(filter, productID, maxResults) # Return result return resThis works, as long as @webapi appears last in the list of decorators. This is because if it appears last it will be the first to wrap the function, and so it will not interfere with the
tg.decorators machinery.
Would it be possible to create a decorator that can be put anywhere among the
decorator list? Yes, it is possible but tricky, and it gives me the feeling
that it may break in any future version of TurboGears:
class webapi(object): def __call__(self, func): def dict_wrap(*args, **kw): return dict(r=func(*args, **kw)) # Migrate the decoration attribute to our new function if hasattr(func, 'decoration'): dict_wrap.decoration = func.decoration dict_wrap.decoration.controller = dict_wrap delattr(func, 'decoration') return dict_wrap # ...in the controller... @expose("json") @validate(validator_dispatcher, error_handler=api_validation_error) @webapi def list_colours(self, filter=None, productID=None, maxResults=100, **kw): # Call API res = self.engine.list_colours(filter, productID, maxResults) # Return result return resAs a convenience, TurboGears 2 offers, in the
decorators module, a way to
build decorator "hooks":
class before_validate(_hook_decorator): '''A list of callables to be run before validation is performed''' hook_name = 'before_validate' class before_call(_hook_decorator): '''A list of callables to be run before the controller method is called''' hook_name = 'before_call' class before_render(_hook_decorator): '''A list of callables to be run before the template is rendered''' hook_name = 'before_render' class after_render(_hook_decorator): '''A list of callables to be run after the template is rendered. Will be run before it is returned returned up the WSGI stack''' hook_name = 'after_render'The way these are invoked can be found in the
_perform_call function in
tg/controllers.py.
To show an example use of those hooks, let's add a some polygen wisdom to every
data structure we return:
class wisdom(decorators.before_render): def __init__(self, grammar): super(wisdom, self).__init__(self.add_wisdom) self.grammar = grammar def add_wisdom(self, remainder, params, output): from subprocess import Popen, PIPE output["wisdom"] = Popen(["polyrun", self.grammar], stdout=PIPE).communicate()[0] # ...in the controller... @wisdom("genius") @expose("json") @validate(validator_dispatcher, error_handler=api_validation_error) def list_colours(self, filter=None, productID=None, maxResults=100, **kw): # Call API res = self.engine.list_colours(filter, productID, maxResults) # Return result return resThese hooks cannot however be used for what I need, that is, to wrap the result inside a dict. The reason is because they are called in this way:
controller.decoration.run_hooks( 'before_render', remainder, params, output)and not in this way:
output = controller.decoration.run_hooks( 'before_render', remainder, params, output)So it is possible to modify the output (if it is a mutable structure) but not to exchange it with something else. Can we do even better? Sure we can. We can assimilate
@expose and @validate
inside @webapi to avoid repeating those same many decorator lines over and
over again:
class webapi(object): def __init__(self, error_handler = None): self.error_handler = error_handler def __call__(self, func): def dict_wrap(*args, **kw): return dict(r=func(*args, **kw)) res = expose("json")(dict_wrap) res = validate(validator_dispatcher, error_handler=self.error_handler)(res) return res # ...in the controller... @expose("json") def api_validation_error(self, **kw): pylons.response.status = "400 Error" return dict(e="validation error on input fields", form_errors=pylons.c.form_errors) @webapi(error_handler=api_validation_error) def list_colours(self, filter=None, productID=None, maxResults=100, **kw): # Call API res = self.engine.list_colours(filter, productID, maxResults) # Return result return resThis got rid of
@expose and @validate, and provides almost all the
default values that I need. Unfortunately I could not find out how to access
api_validation_error from the decorator so that I can pass it to the
validator, therefore I remain with the inconvenience of having to explicitly
pass it every time.
Tim Bunce mentioned my blogpost about
I am sitting in
Couchdb came onto my radar since distributed stuff is interesting to me
these days. But most of what was being written about it put me off, since
it seemed to be very web-oriented, with javascript and html and stuff
stored in the database, served right out of it to web browsers in an
AJAXy mess.
Also, it's a database. I decided a long, long time ago not to mess with
traditional databases. (They're great, they're just not great for me. Said
the guy leaving after 5 years in the coal mines.)
Then I saw Damien Katz's
I had to struggle a bit with the mobile stick I just got from Vodafone and thought it might be useful to write it down somewhere.
The Huawei K3520 (recognized automatically by Linux 2.6.31 as a E620 USB Modem ) seems to work without many problems, the only hardware related issue being that sometimes, seemingly at random, the memory stick part will be loaded and sometimes just the USB modem part.
The Vodafone Germany specific configuration, on the other hand, wasn t so easy to find online. The working config I currently have with NetworkManager 0.7.1 is:

For software, the question whether some software can be considered free is relatively easy to decide: Check the license of the code against the list of free licenses of your trust (
Every time Facebook changes anything, people complain. Most of the time just
because something has changed, without knowing actually what changed.The october layout change for example isn't too big in fact. As far as I can
tell it's not much more than turning the "hot" items that were in the
right sidebar into a special tab (and breaking the refresh for the live feed,
but I guess they'll fix that soon). The "live" tab is basically all
information (see below for getting rid of certain restrictions); the "News"
tab tries to reduce this amount of information by only showing you certain
posts Facebook magic considers to be "important". If you are a heavy user you
will probably prefer the "Live" feed, if you are a casual Facebook user, go
with the "News" feed to have less
Yesterday, via in invitation from fellow Chicago-area
The