2

I am in trying to write a snippet to study Python asyncio. The basic idea is:

  1. use a "simple" web server (aiohttp) to present some data to user

  2. the data return to user will change promptly

here is the code:

import asyncio
import random
from aiohttp import web

userfeed = [] # the data suppose to return to the user via web browsers 

async def data_updater():  #to simulate data change promptly
    while True:
        await asyncio.sleep(3)
        userfeed = [x for x in range(random.randint(1, 20))]
        print('user date updated: ', userfeed)


async def web_handle(request):
    text = str(userfeed)
    #print('in handler:', text)  # why text is empty?
    return web.Response(text=text)

async def init(loop):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', web_handle)
    srv = await loop.create_server(app.make_handler(), '127.0.0.1', 8000)
    print('Server started @ http://127.0.0.1:8000...')
    return srv

loop = asyncio.get_event_loop()
asyncio.ensure_future(data_updater())  
asyncio.ensure_future(init(loop))
loop.run_forever()

the problem is, the code is running (python 3.5), but the userfeed is always empty in browsers and also in web_handler() :-(

  1. why userfeed is not been updated?
  2. regarding this timely date update function, because the update mechanism might be more complex later say async IO wait may be involved, is there a better way instead of using while True: await asyncio.sleep(3) in data_updater() to get "more roughly precise" timer?
Obito
  • 391
  • 3
  • 8
user340307
  • 25
  • 4
  • You want to push notifications to the user browsing your page (without any action from him) ? If so, you should take a look to the *websocket* technology, it is implemented in `aiohttp` in a pretty straightforward way (there is [examples](https://github.com/KeepSafe/aiohttp/blob/master/examples/web_ws.py) in the source code). Otherwise you should use your [`app` object](http://aiohttp.readthedocs.io/en/stable/faq.html#id3) (which provide a `dict` interface) to pass the `userfeed` variable between functions and avoid to make it global. – mgc Oct 04 '16 at 18:50
  • @mgc,thanks for replying. I do not want the data automatically flushing in user's browser, just a client pull model could enough - each time one open the URL via browser or tool like wget , latest data can present. The confuse here is, why the global variable not been updated(inside of `web_handler()` print shows it is always [], but `data _updater()` do print it is changed)? async def is different than normal def it will caching context somehow? – user340307 Oct 04 '16 at 19:17
  • See my answer for an example, as using global variable in this context is explicitly discouraged by the [documentation](http://aiohttp.readthedocs.io/en/stable/web.html#data-sharing-aka-no-singletons-please). But if you really need to make it working using a global `userfeed`, just add `global userfeed` in both `data_updated` and `web_handle` functions and it should works (this way the function know that `userfeed` refers to a variable located in global scope, in your example each function had its own local `userfeed`). – mgc Oct 04 '16 at 19:29

1 Answers1

1

The main problem is that you forget the statement global userfeed in both data_updater and web_handle functions. So, according to how python resolves scopes, in web_handle it was referring to the global variable you defined and in data_updater to a local one, created by the statement userfeed = [x for x .... Using global variables in this context is explicitly discouraged so there is an example using the dict interface of the aiohttp.web.Application object to safely refer to your variable between the functions.

import asyncio
import random
from aiohttp import web


async def data_updater(app):
    while True:
        await asyncio.sleep(3)
        app["userfeed"] = [x for x in range(random.randint(1, 20))]

async def web_handle(request):
    userfeed = request.app["userfeed"]
    return web.Response(text=str(userfeed))

async def init(loop, port=8000):
    app = web.Application(loop=loop)
    app.router.add_route('GET', '/', web_handle)
    handler = app.make_handler()
    srv = await loop.create_server(
        handler, '127.0.0.1', port=port)
    return srv, app, handler

if __name__ == "__main__":
    loop = asyncio.get_event_loop()
    srv, app, handler = loop.run_until_complete(init(loop, 8000))
    app['userfeed'] = []
    asyncio.ensure_future(data_updater(app))
    try:
        loop.run_forever()
    except KeyboardInterrupt:
        pass
    finally:
        srv.close()
        loop.run_until_complete(srv.wait_closed())
        loop.run_until_complete(app.shutdown())
        loop.run_until_complete(handler.finish_connections(60.0))
        loop.run_until_complete(app.cleanup())
    loop.close()

When you refresh the page on 127.0.0.1:8000 you should have some new random numbers as they are updated every 3 seconds server-side (you can put back the print statement in data_updater to verify it).

Community
  • 1
  • 1
mgc
  • 5,223
  • 1
  • 24
  • 37