'Running Quart and Telethon with multiple clients (sessions)

I'm trying to run Quart+Telethon with multiple clients. This example shows one global client. I have this working.

Now I need my app to handle multiple users simultaneously logging in and doing stuff. This post suggests using asyncio.gather.

How do I need to change my code such that I can have multiple people logging in?

Here I placed the bulk of Quart functionality into the work() function (wherever client is referenced). In the def main() I start the app and then invoke asyncio.gather.

When running the app with two work() functions (two clients) in asyncio.gather I get error "AssertionError: Handler is overwriting existing for endpoint phone_form".

And if I run only with one work() function I get a different error: "ConnectionError('Cannot send requests while disconnected')"

What am I missing? Thx

import os
import asyncio

from dotenv import load_dotenv
from quart import Quart, render_template_string, request, render_template
from telethon import TelegramClient


load_dotenv('.env')

API_ID = int(os.getenv('API_ID'))
API_HASH = str(os.getenv('API_HASH'))


app = Quart(__name__)


async def work(client):
    async with client:

        @app.route("/")
        async def phone_form():
            return await render_template('phone_form.html')

        @app.route("/validation_form", methods=['POST'])
        async def validation_form():
            """ Ask the user for the confirmation code they just got. """
            global phone

            # Check form parameters (phone/code)
            form = await request.form
            if 'phone' in form:
                phone = form['phone']
                await client.send_code_request(phone)

            return await render_template('validation_form.html', phone_nr=phone)

async def main():
    import hypercorn.asyncio

    # create task so that starting hypercorn server is no blocking functions that come after it
    server = asyncio.create_task(hypercorn.asyncio.serve(app, hypercorn.Config()))

    # have many clients here, using the app asynchronously
    await asyncio.gather(
        work(TelegramClient('user1', API_ID_, API_HASH)),
        work(TelegramClient('user2', API_ID, API_HASH)),
    )
    # this is to start blocking - means after this subsequent functions will need to wait until hypercorn is finished (hypercorn runs forever!)
    # this await also lets server run indefinitely
    await server

if __name__ == '__main__':
    asyncio.run(main())


Solution 1:[1]

The code is trying to set N function handlers to the same routes, that's what causing the error here. I believe you need a different approach here - make adding sessions a continuous process with routes dealing with the last (new) session.

I'm not sure how hypercorn server should be started so using it's part from your example.

eg.

API_ID = int(os.getenv('API_ID'))
API_HASH = str(os.getenv('API_HASH'))

app = Quart(__name__)

clients = []

async def work():
    new_cli = None
    phone = None
    
    @app.route("/")
    async def index():
        """ Some page containing 'new session' button redirecting to /phone_form """
        new_cli = None
        phone = None
        return render_template('index.html')
        
    @app.route("/phone_form")
    async def phone_form():
        num = len(clients) + 1
        new_cli = TelegramClient(f'user{num}', API_ID_, API_HASH)
        await new_cli.connect()
        return await render_template('phone_form.html')

    @app.route("/validation_form", methods=['POST'])
    async def validation_form():
        """ Ask the user for the confirmation code they just got. """

        # Check form parameters (phone/code)
        form = await request.form
        if 'phone' in form:
            phone = form['phone']
            await client.send_code_request(phone)

        return await render_template('validation_form.html', phone_nr=phone)

    @app.route("/confirm_code", methods=['POST'])
    async def confirm_code():
        """ Finish auth process. """

        form = await request.form
        if 'code' in form:
            await new_cli.sign_in(phone, form['code'])
        clients.append(new_cli)
        new_cli = None
        phone = None

async def main():
    import hypercorn.asyncio

    server = asyncio.create_task(hypercorn.asyncio.serve(app, hypercorn.Config()))
    await work()
    await server

if __name__ == '__main__':
    asyncio.run(main())

You may also want to add checkers if new client session is present. Resulted sessions can be used later.

Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source
Solution 1 Kiril