Asyncio

287 views
Skip to first unread message

Kevin Keller

unread,
Apr 25, 2021, 7:03:30 AM4/25/21
to py4web
Hello, 

I was hoping we can have a discussion one day about asyncio. 

Why? 

The two most popular frameworks in the python world move to asyncio for request handling (flask 2.0 is around the corner with async support and django added channels) plus the new kid on the blog that gains immense momentum (fastapi, asyncio out of the box thx to starlette). 

I think this could be a dealbreaker for some developers if they dont understand why we do not support asyncio because we use bottle for requests. 

Different articles claim performance gains:

I think when we discuss there are several way to address this: 

- A) Writing into the documentation why we think we dont need asyncio for py4web.
(Maybe because we can use greenlets and gevent???) 

- B) Offering ways for developers to add asyncio to their current py4web apps (e.g. embed py4web apps into fastapi.

-C) Using some of the code that needs asyncio and use pydal directly with e.g. fastapi or flask 2.0. 

-D) Consideriations to move from bottle to maybe starlette or sanic etc. for response request handling? 

E) Does pydal even support asyncio?

F) I may not know what I am talking about. 

For what is worth I tested this and it works: 

Sample yatl + pydal code:

requirements: pydal, yatl, fastapi, uvicorn
Then past code into main.py
Uncomment db.inserts so you have some data
run with: uvicorn main:app --reload

from pydal import DAL, Field


db = DAL('sqlite://test.db', folder='dbs')


#try:

#    db.define_table('cars', Field('name'), Field('price', type='integer'))

#    db.cars.insert(name='Audi', price=52642)

#    db.cars.insert(name='Skoda', price=9000)

#    db.cars.insert(name='Volvo', price=29000)

#    db.cars.insert(name='Bentley', price=350000)

#    db.cars.insert(name='Citroen', price=21000)

#    db.cars.insert(name='Hummer', price=41400)

#    db.cars.insert(name='Volkswagen', price=21600)

#

#finally:

#

#    if db:

#        db.close()



from typing import Optional


from fastapi import FastAPI

from fastapi.responses import HTMLResponse

from yatl import render, SPAN


example ="""

{{ for row in rows: }}

<div>

{{=(row.name)}}

</div>

{{ pass }}

"""


app = FastAPI()



@app.get("/",response_class=HTMLResponse)

def read_root():

        rows = db().select(db.cars.ALL)


        return(render(example, context=dict(rows=rows), delimiters="{{ }}"))



Val K

unread,
Apr 25, 2021, 8:38:27 AM4/25/21
to py4web
I never understood half-solutions.
If you are willing to pay with spaghetti code to squeeze all to last drop out of your hardware, it is definitely much more efficient to switch to Assembler!

воскресенье, 25 апреля 2021 г. в 14:03:30 UTC+3, kell...@gmail.com:

Alexander Beskopilny

unread,
Apr 25, 2021, 8:41:09 AM4/25/21
to py4web
Hi!
 
Py4web uses tornado-event-loop  tornado-event-loop uses asyncio
It is also possible to use aiohttp server with asyncio-event-loop or twisted event-loop
I tried py4web tornado with uvloop and saw no difference 
--------------------------------------------------------------------------------------------------------------

# example anyservers.py wiht aiohttp

import logging
from bottle import ServerAdapter

import socketio # pip install python-socketio

anyservers_list = ['wsgirefAioSioWsServer', ]

def wsgirefAioSioWsServer():
    import logging.handlers
    from aiohttp import web
    from aiohttp_wsgi import WSGIHandler  # pip install aiohttp_wsgi

    async def handle(request):
        name = request.match_info.get('name', "Anonymous")
        text = "Hello, " + name
        return web.Response(text=text)

    async def wshandle(request):
        ws = web.WebSocketResponse()
        await ws.prepare(request)

        async for msg in ws:
            if msg.type == web.WSMsgType.text:
                await ws.send_str(f"Echo from ws.aiohttp: {msg.data}")
            elif msg.type == web.WSMsgType.binary:
                await ws.send_bytes(msg.data)
            elif msg.type == web.WSMsgType.close:
                break

        return ws
    sio_debug = False
    sio = socketio.AsyncServer(async_mode='aiohttp')

    @sio.event
    async def connect(sid, environ):
        sio_debug and print('connect ', sid)

    @sio.event
    async def disconnect(sid):
         sio_debug and print('disconnect ', sid)

    @sio.on('to_py4web')
    async def echo(sid, data):
         sio_debug and  print('from client: ', data)
         await sio.emit("py4web_echo", data)

    class AioSioWsServer(ServerAdapter):
        def run(self, app):
            if not self.quiet:
                log = logging.getLogger('loggingAioHttp')
                log.setLevel(logging.INFO)
                log.addHandler(logging.StreamHandler())
            wsgi_handler = WSGIHandler(app)
            app = web.Application()
            sio.attach(app)
            app.router.add_routes( [ web.get('/', wshandle), ])
            app.router.add_route("*", "/{path_info:.*}", wsgi_handler)
            web.run_app(app, host=self.host,  port=self.port)
    return AioSioWsServer


Kevin Keller

unread,
Apr 25, 2021, 8:46:55 AM4/25/21
to Val K, py4web
It's more about gaining developer mind share in the python community as its moving towards async /, await. If it makes sense to us or not.

If we were to switch from bottle to sanic or flask 2.0 instead of bottle there would be no spaghetti code.

The Frankensolutons a proposed were mere thought experiments to support the discussion. 

Look at our sister project emmett :


Pretty much the py4web of the asyncio world. 

It looks very clean to me. 

Anyhow I think py4web needs to address it. 
Bottle has addressed it in this post why asyncio is not strictly needed :



All im saying is it cant be ignored. 

We either don't  support it and relate to the bottle statement and why we think asyncio is garbage (if that is your opinion) or we discuss what can be done about it. 

Either way, ignoring it is the worst outcome I think. 






--
You received this message because you are subscribed to the Google Groups "py4web" group.
To unsubscribe from this group and stop receiving emails from it, send an email to py4web+un...@googlegroups.com.
To view this discussion on the web visit https://groups.google.com/d/msgid/py4web/aecd85af-80c5-404a-a4ed-736d8ce15a42n%40googlegroups.com.

Val K

unread,
Apr 25, 2021, 11:08:25 AM4/25/21
to py4web
py4web (as bottle) is thread-based that allows to utilize single (created at server start) instance of each fixture to serve all request (as the same as  bottle use single request/response object for all request) - as result - speed and efficient memory usage.
 As I understand, to do the same with asyncio you need something like the context managed by framework (hello js-this!).

Otherwise there is no any benefit to fixture-concept as fixtures will turn into trivial helpers/utils, that will be created per each request that will be slower and eat more memory.

As for me,  gevent is the only choice


воскресенье, 25 апреля 2021 г. в 15:46:55 UTC+3, kell...@gmail.com:

Val K

unread,
Apr 25, 2021, 11:28:02 AM4/25/21
to py4web
"However, the number of requests your application can handle at one time will remain the same"

)))))))))
воскресенье, 25 апреля 2021 г. в 18:08:25 UTC+3, Val K:

Kevin Keller

unread,
Apr 25, 2021, 11:35:49 AM4/25/21
to Val K, py4web
Now your last two emails... THAT is exactly the type of arguments I was looking for. 

Maybe we can summarize some statement based on what you said and the bottle documentation has to say about this topic as to why developers dont need an async framework and can happily pick py4web without regrets :).



Val K

unread,
Apr 25, 2021, 12:15:05 PM4/25/21
to py4web
FastAPI +flask works like a proxy, it doesnt make flask app async. And as I know it should spawn thread per request to run flask in it  that means FastAPI should be a "good thread-manager" like gunicorn or uwsgi

воскресенье, 25 апреля 2021 г. в 18:35:49 UTC+3, kell...@gmail.com:

Kevin Keller

unread,
Apr 25, 2021, 12:29:19 PM4/25/21
to Val K, py4web
Yeah but flask 2.0 is coming. you can test the release candidate today's with async support.



Val K

unread,
Apr 25, 2021, 12:48:51 PM4/25/21
to py4web
for async I would go with trio, but thx to asyncio-hype we have  yet another js-like situation (horrible but so many projects/libs/tutorials/courses... too late to change design)


воскресенье, 25 апреля 2021 г. в 19:29:19 UTC+3, kell...@gmail.com:

Massimo

unread,
Apr 25, 2021, 1:18:06 PM4/25/21
to py4web
In my experience async is necessary for websockets but that's it. In all other cases adds problems more than value because of its concurrency model. One request may lock other requests and does not add performances. In particular pydal is not async io compliant because not the adapters work with async. I never had problems with threads.

I think py4web needs a buil-in websocket async based solution. The problem here is scalability. Imagine implementing a simple chat with a single channel. Each message needs to be broadcasted to all connected websockets. How is this supposed to work behind a load balancer and clients randomly connected to multiple py4web server instances? In my experiences one websocket server can only handle ~1000 connected clients. I think the solution is having multiple async websocket server talking downstream to clients and sharing data upstream with a "root" server (maybe implemented in redis or py4web itself). Unless we can make this simple and fool-proof for arbitrary channels with authentication I do not think we have a good solution.

Massimo

Val K

unread,
Apr 25, 2021, 1:22:12 PM4/25/21
to py4web
It reminds me of an old Russian joke-story: 
u2 comes to Russia to perform, but at the concert people chant "b2-b2-b2". Bono says like "I'm sorry, but we're not b2 we are u2", but people keep shouting "b2-b2-b2", Bono says "well..., okay" and begins to sing this song  

Flask guys look like u2 at that concert)))))
воскресенье, 25 апреля 2021 г. в 19:48:51 UTC+3, Val K:

Massimo

unread,
Apr 25, 2021, 1:46:13 PM4/25/21
to py4web
LOL

Alexander Beskopilny

unread,
Apr 26, 2021, 3:49:33 AM4/26/21
to py4web
practical tip
the only place for async-await in py4web is in the file anyservers.py

interesting problems-async-await-questions :
1 for example it is not very clear how to attach aioredis to tornado without locking tornado (and py4web)

2 also, it is not clear how to achieve an increase in performance of tornado using uwloop

3 how to make a reliable message system (like elixir-phoenix) between the server and the application

4 how will using twisted affect py4web performance (if twisted works with py4web)

please share your thoughts on these topics

Alexander Beskopilny

unread,
Apr 29, 2021, 7:00:36 PM4/29/21
to py4web
#  The twisted  probably works, too
#  
        """
            tested with py4web apps from https://github.com/ali96343/facep4w
            and 10 threads https://github.com/linkchecker/linkchecker
            pydal tested with p4wform (scan url, find forms and insert value)
        """
#   pip install autobahn
#   pip install Twisted

from autobahn.twisted.websocket import WebSocketServerFactory, WebSocketServerProtocol
from autobahn.twisted.resource import WebSocketResource, WSGIRootResource

from twisted.web.resource import Resource

class Hello(Resource):
    isLeaf = True
    def getChild(self, name, request):
        if name == '':
            return self
        return Resource.getChild(self, name, request)

    def render_GET(self, request):
        request.setHeader("Content-Type", "text/html; charset=utf-8")
        return "<html>its my-favicon-robots</html>".encode('utf-8')

class WsEcho(WebSocketServerProtocol):

    def onMessage(self, payload, isBinary):
        self.sendMessage(payload, isBinary)

    def onClose(self, wasClean, code, reason):
        sio_ws_debug and  print ( "WebSocket connection closed: " )

    def onConnect(self, request):
        sio_ws_debug and print("Client connecting: {0}".format(request.peer))

    def onOpen(self):
        sio_ws_debug and print("WebSocket connection open.")
def wsgirefWsTwistedServer():

    from twisted.python import log
    import sys
    class TwistedServer(ServerAdapter):
        def run(self, handler):
            from twisted.web import server, wsgi
            from twisted.python.threadpool import ThreadPool
            from twisted.internet import reactor

            #log.startLogging(sys.stdout)

            wsFactory = WebSocketServerFactory(f"ws://{self.host}:{self.port}/")
            wsFactory.protocol = WsEcho
            wsResource = WebSocketResource(wsFactory)

            myfavi = Hello()

            thread_pool = ThreadPool()
            thread_pool.start()
            reactor.addSystemEventTrigger('after', 'shutdown', thread_pool.stop)

            wsgiResource = wsgi.WSGIResource(reactor, thread_pool, handler,  )
            rootResource = WSGIRootResource(wsgiResource,
                 {b'': wsResource, b'favicon.ico': myfavi, b'robots.txt':myfavi})

            factory = server.Site( rootResource  )

            reactor.listenTCP(self.port, factory, interface=self.host)
            reactor.run()

    return TwistedServer

Kevin Keller

unread,
May 3, 2021, 4:38:42 PM5/3/21
to Alexander Beskopilny, py4web
I think this video from one of the main flask maintainers how django and flask 2.0 being wsgi frameworks support async is extremely insightful :


Especially at minute 14 where he talks about how is achieved.

Apparently it's using https://github.com/django/asgiref


Is there something for us to take away from?

Can we leverage asgiref too?



Reply all
Reply to author
Forward
0 new messages