-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Hi Krishnakant,

On 19 Dec 2009, at 09:35, Krishnakant wrote:
> I thought I would put this question in a different way, not just to  
> keep
> it on-topic for pylons list and make my mis-understanding clear about
> the issue at hand.

Understood - and answered in a similar spirit ...

> My ownly question is that when we do @jsonify, do we need to say which
> action we want to use for returning a json response or just putting  
> the
> @jsonify asumes that the action following that line is the one to be
> used for responding to json requests?


Following up on Mario's post ...

"@jsonify" is an example of a Python decorator. Unfortunately, the  
concept of a "decorator" in Python is not terribly well documented as  
demonstrated by a couple of probes of the official Python  
documentation (don't bother following these two links, KK, they'd just  
be a waste of your time):

http://docs.python.org/search.html?q=decorators
http://docs.python.org/glossary.html#term-decorators

Probably the best overview description of decorators appears in  
Python's wikipedia entry (keep it for later, KK):

http://en.wikipedia.org/wiki/Python_syntax_and_semantics#Decorators

Let's see if we can produce a succinct distillation from the wikipedia  
entry:

"A decorator is a Python object that can be called with a single  
argument and which modifies a function or a method. Python decorators  
were inspired in part by Java annotations, and have a similar syntax;  
the decorator syntax is pure syntactic sugar and uses "@" as the  
keyword, e.g. "@jsonify". Decorators are a form of metaprogramming;  
they enhance the action of the function or method they decorate."

To decorate a function or a method, simply reference the decorator in  
the line immediately prior to the function/method definition, as shown  
in this pseudo-code:

> @jsonify
> def entityname(self, id):
>     dbentity = model.Session.query(model.MyEntity).get(id)
>     return {"name": dbentity.name}

Decorators are often used as "convenience functions" (to use a broad  
term, broadly), here's the (slightly edited) Pylons code for @jsonify:

> import simplejson
> from decorator import decorator
> from pylons.decorators.util import get_pylons
>
> def jsonify(func, *args, **kwargs):
>     """Action decorator that formats output for JSON
>
>     Given a function that will return content, this decorator will  
> turn
>     the result into JSON, with a content-type of 'application/json'  
> and
>     output it.
>
>     """
>     pylons = get_pylons(args)
>     pylons.response.headers['Content-Type'] = 'application/json'
>     data = func(*args, **kwargs)
>     if isinstance(data, (list, tuple)):
>         msg = "JSON responses with Array envelopes are susceptible  
> to " \
>               "cross-site data leak attacks, see " \
>               "http://pylonshq.com/warnings/JSONArray";
>         warnings.warn(msg, Warning, 2)
>         log.warning(msg)
>     log.debug("Returning JSON wrapped action output")
>     return simplejson.dumps(data)
>
> jsonify = decorator(jsonify)


[ BTW, the URL referenced in the warning doesn't actually resolve, so  
you'll just have to use your imagination as to exactly how dire is the  
detail of the warning about JSONArrays :-) ]

As you can see, it's a convenience. One could almost as easily set the  
response header oneself and similarly return the result of  
simplejson.dumps(data).

Note: as Mario observed, when using Pylons, you don't need to return a  
rendered template. If you wanted, your action could be as simple as:

> def test_action(self):
>     return "Hello world"


or

> def test_action(self):
>     return ["Hello world"]

Basically, you can return anything you like, just so long as it can be  
iterated. It's a WSGI thing rather than anything Pylons-specific but  
it explains why you can return just simplejson.dumps(data) without  
having to use a template as a vehicle for carrying the result.

Sometimes the convenience offered by decorators is significant. Here  
is the code for an "@authorize" decorator (used in the Shabti  
auth'n'auth augmented Pylons project template) which can be used to  
guard a controller action by imposing access-control permissions:

> def authorize(permission):
>
>     """Decorator for authenticating individual actions. Takes a  
> permission
>     instance as argument(see lib/permissions.py for examples)"""
>     def wrapper(func, self, *args, **kw):
>         if permission.check():
>             return func(self, *args, **kw)
>         pylons.session['redirect'] = \
>                 pylons.request.environ['pylons.routes_dict']
>         pylons.session.save()
>         redirect_to_login()
>     return decorator(wrapper)


Here's an example of a permission instance:

> class SignedIn(object):
>     def check(self):
>         return (get_user() is not None)


and the decorator is used typically thus (minimalist code, purely as  
an example):

> @authorize(SignedIn())
> def delete(self, id):
>     dbentity = get_object_or_404(model.MyEntity, id=id)
>     model.Session.delete(dbentity)
>     model.Session.commit()

The @authorize decorator causes the permissions code to execute before  
the method/action is called. If the user is not authenticated, they  
are redirected to a login page. If the user /is/ authenticated, the  
decorator code simply calls the controller action.

Judicious use of decorators can keep your controller code quite  
tightly focused on the data marshalling task that it's directly aimed  
at, leaving the decorators to handle ancillary-yet-necessary  
functionality such as protecting from XSS and other infections. This  
is in addition to potentially handling other useful ancillary  
functionality that also results in controller actions being neatly  
focused on the task in hand.

I endorse Mario's reference of 
http://pylonsbook.com/en/1.1/css-javascript-and-ajax.html#json 
  - that URL will position you accurately in that chapter at James'  
useful explanation of using JSON with Pylons. It is also very likely  
that you will find the prior sections of that chapter significantly  
useful as they deal with Ajax and debugging Ajax requests as well  
presenting an extensive description of using YUI, which IIRC you  
specifically enquired about. If you come across anything in there that  
you need explaining differently or you need to have a missing  
"longdesc" for an img provided, feel free to ask. (You have my email  
address and I will be happy to help).

You may not have picked up on this yet but ... the PylonsHQ web site  
is a Pylons instance, written by Ben Bangert and the source code is  
available on bitbucket [1]. I have found it an invaluable guide to the  
practice of using Pylons code and concepts. For example, here is a  
chunk from the account registration controller showing how Ben uses  
decorators to excellent effect:

> @rest.dispatch_on(POST='_process_login')
> def login(self):
>     redir = request.GET.get('redir')
>     if redir and redir.startswith('/'):
>         session['redirect'] = str(redir)
>         session.save()
>     return render('/accounts/login.mako')
>
> @validate(form=forms.login_form, error_handler='login')
> @secure.authenticate_form
> def _process_login(self):
>     user = self.form_result['user']
>     user.process_login()
>     success_flash('You have logged into PylonsHQ')
>     if session.get('redirect'):
>         redir_url = session.pop('redirect')
>         session.save()
>         redirect_to(redir_url)
>     redirect_to('home')

Taking a quick look at the use of the "@rest" decorator (without going  
into tedious detail), a GET request on "/accounts/login" causes the  
method to execute, returning the rendered template "login.mako".  
However a POST request to "/accounts/login" causes a dispatch to the  
_process_login method which is then called (in turn) by the attendant  
decorator chain. Ben's use of "@validate" and "@secure" decorators for  
_process_login shows how decorators can easily be "chained" - they are  
simply executed in order of appearance, each one "guarding" the next.

I guess there's only one thing left to cover which is: how to get a  
controller action to produce json on request. You have a couple of  
choices:

1. Create a dedicated action, e.g. "fetch_model_as_json" and (as shown  
above) either decorate it with @jsonify or handle the simplejson.dumps  
of the data yourself, then call the action explicitly via Ajax or  
whatever: /mycontroller/fetch_model_as_json"

2. Use Routes' "RESTful service" [2] to pass through a "format"  
specifier. As the Routes manual describes: "Several routes are paired  
with an identical route containing the format variable. The intention  
is to allow users to obtain different formats by means of filename  
suffixes; e.g., “/messages/1.xml”. This produces a routing variable  
“xml”, which in Pylons will be passed to the controller action if it  
defines a formal argument for it."

For example, you can generate a format-specific request like this:

> url("message", id=1, format="json")  =>  "/messages/1.json"


the "format" keyword arg and its bound value will be available when  
the action is called:

> def view(self, id, format='html'):
>     if format == 'json':
>         # return some json
>     else:
>         # return some HTML, possibly via a rendered mako template


Then you can call "messages/1" and get HTML returned or call "/message/ 
1.json" and get JSON returned (or, if you've coded for it) call "/ 
message/1.xml" and get XML returned.

HTH.

References:

[1] http://bitbucket.org/bbangert/kai/
[2] http://routes.groovie.org/manual.html#restful-services


(KK, I tried to distinguish code from narrative by quoting it. Is that  
an effective approach to take for use with a screen reader? If there's  
a specific style that suits a screen reader better, then I'd be happy  
to learn of it.)


Cheers,

Graham

http://www.linkedin.com/in/ghiggins




-----BEGIN PGP SIGNATURE-----

iEYEARECAAYFAkss934ACgkQOsmLt1NhivziDQCgiL77dqtYGxe4wletiWOd8nzj
Tg4An1MesrWn9rWZXReUcfXk/ttVY3SNiQCVAgUBSyz3flnrWVZ7aXD1AQKexwP/
Yaw7/jqnqc7IvX0zgWLa8pi0CxMoPto7SE62VWUQ3hBOerXGt6KO8nsgZgPNlL6g
v+dVnftQJm0Lt/2L6krXQolwBmjx4otcA26RRryfVfKPyFAIqL8PHEYzXnumNCmS
TSwt0UH5qGPmLTb2IOUAe1WsWnMMEMyc/WZ2WqfBkV0=
=MqSG
-----END PGP SIGNATURE-----

--

You received this message because you are subscribed to the Google Groups 
"pylons-discuss" group.
To post to this group, send email to [email protected].
To unsubscribe from this group, send email to 
[email protected].
For more options, visit this group at 
http://groups.google.com/group/pylons-discuss?hl=en.


Reply via email to