tornado 3.0 callback

197 views
Skip to first unread message

Алексей Силк

unread,
Mar 30, 2013, 12:39:37 PM3/30/13
to python-...@googlegroups.com
Have somebody tryed to use new callback?
Any examples?

* The ``callback`` argument to many asynchronous methods is now
  optional, and these methods return a `.Future`.  The `tornado.gen`
  module now understands ``Futures``, and these methods can be used
  directly without a `.gen.Task` wrapper.

Ben Darnell

unread,
Mar 30, 2013, 5:13:25 PM3/30/13
to Tornado Mailing List
The gen module's documentation has been revised to use the new style:

To call a function using futures, just remove the gen.Task wrapper, call the function normally, and yield the result.  That is,
    yield gen.Task(http_client.fetch, url)
becomes
    yield http_client.fetch(url)

To write a function that can be called in this way, decorate it with either @return_future or @gen.coroutine.  To convert a function that uses @gen.engine to use @gen.coroutine instead, remove the callback argument and instead of running a callback when the operation is complete, raise the special exception gen.Return(value).  That is,
    @gen.engine
    def my_async_function(args, callback):
        result = yield do_something_async()
        callback(result)

becomes
    @gen.coroutine
    def my_async_function(args):
        result = yield do_something_async()
        raise gen.Return(result)

The gen.engine version can only be called with gen.Task or an explicit callback; the gen.coroutine version can be called directly to yield a Future.

-Ben


--
You received this message because you are subscribed to the Google Groups "Tornado Web Server" group.
To unsubscribe from this group and stop receiving emails from it, send an email to python-tornad...@googlegroups.com.
For more options, visit https://groups.google.com/groups/opt_out.
 
 

Алексей Силк

unread,
Mar 30, 2013, 11:32:32 PM3/30/13
to python-...@googlegroups.com
Thank you. It was very helpfull :)

суббота, 30 марта 2013 г., 20:39:37 UTC+4 пользователь Алексей Силк написал:

Алексей Силк

unread,
Mar 30, 2013, 11:41:59 PM3/30/13
to python-...@googlegroups.com
Old way is still working. But what is the difference between new way and old way? Maybe new way takes less time?


суббота, 30 марта 2013 г., 20:39:37 UTC+4 пользователь Алексей Силк написал:
Have somebody tryed to use new callback?

Алексей Силк

unread,
Mar 31, 2013, 12:14:22 AM3/31/13
to python-...@googlegroups.com
Also, if I use @gen.coroutine how to send args?

response = yield gen.Task ( self.get_ftpusers_by_userid, {'userid': self.get_secure_cookie("user") } )
I used that in old way.

now it looks like 

response = yield self.get_ftpusers_by_userid, {'userid': self.get_secure_cookie("user") }

But I get errors

      File "/home/rootiks/py/cloudftps/production/myftpbackup/tornado/gen.py", line 537, in run
        self.exc_info = (BadYieldError("yielded unknown object %r" % yielded),)
    TypeError: not all arguments converted during string formatting




суббота, 30 марта 2013 г., 20:39:37 UTC+4 пользователь Алексей Силк написал:
Have somebody tryed to use new callback?

Nicholas Dudfield

unread,
Mar 31, 2013, 12:31:19 AM3/31/13
to python-...@googlegroups.com
It looks like you yielded a tuple ?

Алексей Силк

unread,
Mar 31, 2013, 5:38:16 AM3/31/13
to python-...@googlegroups.com
Ok ...

Here is some code ...

            @gen.coroutine
            def dostupkhranilishuFtpUsers(*args,**kwargs):
                response = yield self.get_ftpusers_by_userid( dict ( userid = self.get_argument("user") )  )
                q = self.render_string("storage-ftpusers.html", ppFtpUsers = response )
                if (q): self.write(q)
                else :
                    self.write("500")


    @gen.coroutine
    def get_ftpusers_by_userid(self, data):
        result = self.db.query("SELECT users.id, users.userid, users.serversid, users.status,"
                             "WHERE users.allusersid=%s AND users.deleted='false' ", data['userid'])
        raise gen.Return(result)


Does it correct? I mean do I understand good how to use new way of async?

horace

unread,
Mar 31, 2013, 4:20:09 AM3/31/13
to python-...@googlegroups.com
It turns out this is cause by the Content-Type setting:

On the BaseHandler:

def prepare(self):
if self.request.headers.get("Content-Type") == "application/json":
self.json_args = tornado.escape.json_decode(self.request.body)

But actually in the request header:

'Content-Type': 'application/json; charset=utf-8'

This string doesn't match at all. But even after setting the Content-Type = "application/json" from the client, the string appended "; charset=utf-8" again. The only way I fixed this is change the basehandler code to:

def prepare(self):
if self.request.headers.get("Content-Type") == "application/json; charset=utf-8":
self.json_args = tornado.escape.json_decode(self.request.body)

It fixed my problem. But still, anybody know where "charset=utf-8" come from? Is it automatically set up by AFNetworking?

Thanks!


On Mar 31, 2013, at 1:38 PM, horace <contact...@gmail.com> wrote:

> I am trying to use AFHTTPClient to connect with a tornado server(3.0). Here is my code from client:
>
> - (id)initWithBaseURL:(NSURL *)url {
> self = [super initWithBaseURL:url];
> if (!self) {
> return nil;
> }
>
>
> [self setDefaultHeader:@"User-Agent" value:@"Client"];
>
> [self setDefaultHeader:@"Accept" value:@"application/json"];
> self.parameterEncoding = AFJSONParameterEncoding;
> [self registerHTTPOperationClass:[AFJSONRequestOperation class]];
>
> On the server side, I setup the handler as bellow:
>
>
> class BaseHandler(tornado.web.RequestHandler):
> @property
> def db(self):
> return self.application.db
>
> def prepare(self):
> if self.request.headers.get("Content-Type") == "application/json":
> self.json_args = tornado.escape.json_decode(self.request.body)
>
>
> Then built a subclass of this handler:
>
>
> class TimelineHandler(BaseHandler):
> def post(self):
> user_id = self.json_args.get("user_id")
> device_id = self.json_args.get("device_id")
> token = self.json_args.get("token")
>
> //do something with the request
>
> self.write(response)
>
> But when I run this code, it failed with following ERROR message:
>
>
> HTTPRequest(protocol='http', host=<CORRECT URL HERE>, method='POST', uri='CORRECT URL HERE', version='HTTP/1.0', remote_ip='127.0.0.1', body='{"user_id":1,"device_id":"b9af8d9039ec1e527fecca70caf486e1","token":"36c9a0fe-2c4f-4273-9e12-0f855e05de87"}', headers={'Content-Length': '107', 'Accept-Language': 'en;q=1, fr;q=0.9, de;q=0.8, ja;q=0.7, nl;q=0.6, it;q=0.5', 'Accept-Encoding': 'gzip, deflate', 'X-Scheme': 'http', 'Host': 'CORRECT URL HERE', 'Accept': 'application/json', 'User-Agent': 'Client', 'Connection': 'close', 'X-Real-Ip': '58.246.153.177', **'Content-Type': 'application/json**; charset=utf-8'})
> Traceback (most recent call last):
> File "/usr/local/lib/python2.7/dist-packages/tornado/web.py", line 1077, in _execute
> *self.path_args, **self.path_kwargs)
> File "/server.py", line 128, in post
> user_id = self.json_args.get("user_id")
> **AttributeError: 'TimelineHandler' object has no attribute 'json_args'**
>
> The strange thing is, in the request header, it's clearly showing 'Content-Type': 'application/json. Then why the handler still has no attribute 'json_args'?
>
> Thanks you very much if you could give me any advice.
>
> -Horace

horace

unread,
Mar 31, 2013, 1:38:14 AM3/31/13
to python-...@googlegroups.com

Ben Darnell

unread,
Mar 31, 2013, 5:49:03 PM3/31/13
to Tornado Mailing List
There is nothing asynchronous here.  You've given these methods asynchronous interfaces, but since self.db.query is synchronous it doesn't do any good.  For the asynchronous interfaces you added to be useful, you must do something else asynchronous somewhere along the way so that other operations have a chance to proceed.  If your database interface were asynchronous (so you could say "result = yield self.db.query(...)", then this would be a correct way to make asynchronous functions that use it.

-Ben

Ben Darnell

unread,
Mar 31, 2013, 6:02:30 PM3/31/13
to Tornado Mailing List
The new way has a number of small advantages:
* Function calls look like function calls; no need to wrap everything in gen.Task.  (this is admittedly a bit of a double-edged sword; forgetting a "yield" now results in a less-clear error message than forgetting a "yield gen.Task")
* Returning via an exception instead of a manual callback makes early "returns" less error-prone.  In python 3.3 it's even better since you can use the return statement.
* Futures provide consistent error handling even to code that uses manual callbacks.  By standardizing on Futures we won't need adapter classes like motor.Op for interfaces that define their own error handling.

-Ben


--

Nicholas Dudfield

unread,
Mar 31, 2013, 9:18:04 PM3/31/13
to python-...@googlegroups.com
>>> in python 3.3 it's even better since you can use the return statement.

`yield from` rocks too :)

Aleksey Silk

unread,
Mar 31, 2013, 11:36:40 PM3/31/13
to python-...@googlegroups.com
Thank you for your answer Ben.
For now if I need to transfer args and kwargs to function with mre than one action, I still need to use gen.Task with @gen.engine decorator.
@gen.coroutine is for only one action as I understood.



С уважением, Алексей Силк
With best regards, Aleksey Silk
 
skype - rootiks
 


2013/4/1 Nicholas Dudfield <ndud...@gmail.com>

Ben Darnell

unread,
Apr 1, 2013, 9:21:26 AM4/1/13
to Tornado Mailing List
I'm not sure what you mean about "more than one action", but you can use gen.coroutine anywhere you can use gen.engine.  You pass arguments by simply calling the function, as you did in your example when you called yield self.get_ftpusers_by_userid( dict ( userid = self.get_argument("user") )  )

-Ben
Reply all
Reply to author
Forward
0 new messages