Giter Club home page Giter Club logo

Comments (12)

pgjones avatar pgjones commented on August 17, 2024

This is a common issue with Flask and Quart contexts, you can solve it via the copy_current_app_context decorator, as so,

Thread(
      target=copy_current_app_context(send_async_email),
      args=(current_app._get_current_object(), msg),
    ).start()

The issue is that the context (with Quart 0.7 onwards) is copied for you into different asyncio tasks, however it isn't copied into a different Thread. The explicit usage of the copy_current_app_context does so though.

from quart.

slyduda avatar slyduda commented on August 17, 2024

I am getting the following warning:

RuntimeWarning: coroutine 'wrapper' was never awaited
  self._target(*self._args, **self._kwargs)

The wrapped function does not run either.

from quart.

pgjones avatar pgjones commented on August 17, 2024

Ah yes as the copy_current_app_context returns a coroutine function rather than a function. You need to run an event loop on the other thread.

Are you trying to run a sync or async function in another thread? Also are you trying to invoke the thread from a sync or async context?

from quart.

slyduda avatar slyduda commented on August 17, 2024

Sync in the Thread as shown in the example above. I am invoking from an async context within one of the Quart_OpenAPI endpoints:

def send_email(_app, message):
    with _app.app_context():
        mail.send(message)

@app.route('/test')
class Test(Resource):
    async def get(self):
        message = Message(sender=app.config.get('MAIL_USERNAME'),
                          recipients=[app.config.get('MAIL_USERNAME')],
                          subject='TEST')

        Thread(
            target=copy_current_app_context(send_email),
            args=(
                current_app._get_current_object(),
                message
            )
        ).start()

        response = {
            'message': 'New registration email sent.'
        }

        return jsonify(response), 200

from quart.

slyduda avatar slyduda commented on August 17, 2024

Additionally, this is what I tried to run an event loop on the other thread, but I am still getting the same warning:


def send_email(_app, _loop, message):
    with _app.app_context():
        print('====> sending on a separate thread.')
        asyncio.set_event_loop(_loop)
        _loop.run_until_complete(
            mail.send(message)
        )


@app.route('/test')
class Test(Resource):
    async def get(self):
        message = Message(sender=app.config.get('MAIL_USERNAME'),
                          recipients=[app.config.get('MAIL_USERNAME')],
                          subject='TEST')

        thr = Thread(
            target=copy_current_app_context(send_email),
            args=(
                current_app._get_current_object(),
                asyncio.new_event_loop(),
                message
            )
        ).start()

        response = {
            'message': 'New registration email sent.'
        }

        return jsonify(response), 200

from quart.

slyduda avatar slyduda commented on August 17, 2024

Sorry to spam, this technique has failed me as well: https://pgjones.gitlab.io/quart/background_tasks.html

from quart.

pgjones avatar pgjones commented on August 17, 2024

Does this work?

from contextvars import copy_context
from functools import partial

async def run_sync(func) -> Any:
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(
        None, copy_context().run, func,
    )

def send_email(message):
    with app.app_context():
        print('====> sending on a separate thread.')
        mail.send(message)

@app.route('/test')
async def get(self):
    message = Message(
        sender=app.config.get('MAIL_USERNAME'),
        recipients=[app.config.get('MAIL_USERNAME')],
        subject='TEST',
    )
    await run_sync(partial(send_email, message))

If so it will get a lot easier in the next Quart release (if not I need to do some more work).

from quart.

slyduda avatar slyduda commented on August 17, 2024

Unfortunately, I get the following:

 File "app.py", line 100, in send_email
    with app.app_context:
AttributeError: __enter__

from quart.

pgjones avatar pgjones commented on August 17, 2024

Hmm tricky, this then needs to allow the execution of sync code in a thread surrounded by an async context setup. Does this work?

from contextvars import copy_context
from functools import partial

async def run_sync(func) -> Any:
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(
        None, copy_context().run, func,
    )

def send_email(message):
    print('====> sending on a separate thread.')
    mail.send(message)

@app.route('/test')
async def get(self):
    message = Message(
        sender=app.config.get('MAIL_USERNAME'),
        recipients=[app.config.get('MAIL_USERNAME')],
        subject='TEST',
    )
    async with app.app_context():
        await run_sync(partial(send_email, message))

I've wrapped the run sync in the app context setup, which should keep it set during the run_sync execution. As run_sync copies the context it should also be available in the send_email function...

from quart.

slyduda avatar slyduda commented on August 17, 2024

This works!

Pardon my confusion though, I was under the impression that the intent of the original post was to create a function on a new thread to provide the end user with a response while dealing with the mail server connection asynchronously. With this fix it is still blocking my response. Maybe the confusion stemmed from my choice to make the send mail function synchronous, though I kept getting a never awaited error when attaching it to the thread.

from quart.

pgjones avatar pgjones commented on August 17, 2024

Ah, the above solution ensures that the actual sending of the message doesn't block the event loop thereby allowing the app/server to serve other requests concurrently. To achieve a background send you will need to use a background task as well,

from contextvars import copy_context
from functools import partial

async def run_sync(func) -> Any:
    loop = asyncio.get_running_loop()
    return await loop.run_in_executor(
        None, copy_context().run, func,
    )

def send_email(message):
    print('====> sending on a separate thread.')
    mail.send(message)

@app.route('/test')
async def get():
    async def _get():
        message = Message(
            sender=app.config.get('MAIL_USERNAME'),
            recipients=[app.config.get('MAIL_USERNAME')],
            subject='TEST',
        )
        async with app.app_context():
            await run_sync(partial(send_email, message))
    asyncio.ensure_future(_get())
    return "Pending", 201

from quart.

slyduda avatar slyduda commented on August 17, 2024

This above code is perfect! Thank you so much for the help.

from quart.

Related Issues (20)

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.