Skip to content

IMPROVEMENT: Support for asynchronous sending #296

Open

Description

@Jakobovski

Thanks for the great API and product!

It would be awesome if there was support for asynchronous api calls.

Use Case:
When charging a user's credit card over my REST API I need to call the stripe API ( a few times), then call sendgrid API to send user confirmation message, then call sendgrid again to notify my sales team of a new sale. This takes a bit of time to wait for all the responses and causes my API it be rather slow.

Cheers.

Activity

  1. thinkingserious commented on Feb 20, 2017

    @thinkingserious
    Contributor

    Hello @Jakobovski,

    Thanks for taking the time to not only add the request, but for the additional details! And thanks for the kind words as well!

    We will add this to our backlog. For it to rise in importance, we would require additional votes or a pull request.

    With Best Regards,

    Elmer

  2. jussih commented on Oct 3, 2017

    @jussih

    This is something I wouldn't expect in a library, but rather something that should be implemented in the project that uses the library. The de facto solution with Python is to write an asynchronous task to do the heavy lifting using Celery.

  3. thinkingserious commented on Oct 4, 2017

    @thinkingserious
    Contributor

    Hi @jussih,

    Would you mind creating documentation that demonstrates how this can be done? You would add your PR here and we would give you hacktoberfest credit at "difficulty: medium" for that. If not, no worries. We appreciate your feedback in any case :)

    With Best Regards,

    Elmer

  4. LiYChristopher commented on Oct 4, 2017

    @LiYChristopher
    Contributor

    Celery is a great tool for async or parallel processing. But if one is using Python 3.5+, the built-in asyncio library can be used to send email in a non-blocking manner. I created a gist to illustrate roughly how this might work.

    SendGrid v3 Mail Send - Async Example

    asyncio helps us execute mail sending in a separate context, allowing us to continue execution of business logic without waiting for all our emails to send first.

  5. tr11 commented on Oct 16, 2017

    @tr11

    I've been doing something like this for a while, but resorted to updating the http-client library instead of making any changes to this library. You can take a look at https://github.com/tr11/python-http-client, which uses aiohttp instead of the requests library to do the heavy lifting.

  6. thinkingserious commented on Oct 16, 2017

    @thinkingserious
    Contributor

    @LiYChristopher,

    Do you mind adding that example to this repo's USE_CASES.md?

    That's awesome @tr11!

    Do you mind making a PR for hacktoberfest on that library to demonstrate your example? You would create a USE_CASES.md based on the format of the one found in this repo.

    With Best Regards,

    Elmer

  7. LiYChristopher commented on Oct 16, 2017

    @LiYChristopher
    Contributor

    @thinkingserious

    I have a PR open - #363 that adds this example to the USE_CASES.md file. If there are any other changes I need to make beyond contained in the change log, please let me know. Thanks!

  8. dizlv commented on Apr 10, 2018

    @dizlv

    @LiYChristopher are you sure your code actually works? Client post will block scheduler, or I'm missing something?

    response = sg.client.mail.send.post(request_body=email.get())

  9. stalkerg commented on Apr 28, 2019

    @stalkerg

    Hello.
    I suppose we can use the same approach what I used for https://github.com/stalkerg/pywebpush - sendgrid API will return just URL and data to send, and all sending mechanisms should be outside.
    Like

    sg = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY'))
    url, data = sg.prepare_send(message)
    response = await your_async_method_for_send(url, data)
    response = sg.process_response(response)

    because we have many different libs (for me it's Tornado) to make an async request we should provide an easy way to integration with any of it.

  10. ejm commented on Sep 29, 2019

    @ejm

    If this is still on the table, I'd love to take it on as a Hacktoberfest project two years later following the idea of @stalkerg here. Would the method name prepare_send be okay? I wouldn't imagine a process_response method would be necessary as send just returns the response untouched.

    I think send should keep the default client (for backwards compatibility) but prepare_send would just be another option for people who are using async or their own HTTP clients.

  11. Taywee commented on Nov 20, 2020

    @Taywee

    This has been open for a while, and nobody has mentioned it, but you can run blocking network requests in parallel through the default executor. It's not an obvious solution (mentioned only in a corner of the asyncio documentation and referring to a method that doesn't directly refer to being run in a background thread, but the example code demonstrates it), and it's not perfect (because it's not actually using async IO, but executing parallel through threads behind the scenes) but this will work when run from within any async function, and will be properly parallelized without blocking or exposing thread safety issues:

    mail = {
        # Mail body here
    }
    loop = asyncio.get_running_loop()  # or get_event_loop if you need to use Python 3.6
    response = await loop.run_in_executor(
        None,
        functools.partial(
            api_client.client.mail.send.post,
            request_body=mail))

    Like I said, not the most ideal, but for my uses, it allows backgrounding parallel IO to keep my application responsive and snappy.

    The previous answer posted by @LiYChristopher will not work. Each request will still fully block the scheduler. I only mention it because it has a handful of positive reactions despite not functioning the way it looks like it does.

    edit: I should mention that this is thread-safe in respect to asyncio, but it does involve threads running in parallel, so if sendgrid's API client is not thread-safe for any reason, this will actually be vulnerable to thread safety issues. I don't think this will be the case in cPython due to the GIL, but I can't assert that for certain. I have had exactly one 503 error from SendGrid while interacting in this way, but I don't think it's related to this parallelism and I haven't been able to replicate it.

  12. stalkerg commented on Nov 22, 2020

    @stalkerg

    @Taywee, after all, I started to communicate with REST API directly it's much easier than fighting with the library in the async environment.

  13. Taywee commented on Nov 23, 2020

    @Taywee

    @stalkerg
    That's fine. The SendGrid API is pretty easy to work with directly. This library is synchronous, though, so the only way to get parallel IO with it currently is through a separate thread or process, whether using traditional threading, an asynchronous executor (including the default ThreadPoolExecutor that the default Python event loop makes available), or some other way of offloading the parallel work to a thread and waiting for it.

  14. thinkingserious commented on Dec 1, 2020

    @thinkingserious
    Contributor

    I prefer @stalkerg's solution, mainly because there are several popular async libs and it would be great to better support them all. Then we can add usage examples in the most popular async libs in the docs.

    Thank you to everyone on this thread (and linked threads) for the thoughtful conversation. And thank you @Taywee for the PR and explanations with sample code!

    This issue has been added to our internal backlog to be prioritized. Pull requests and +1s on the issue summary will help it move up the backlog.

  15. added and removed
    status: work in progressTwilio or the community is in the process of implementing
    on Dec 1, 2020
  16. stalkerg commented on Dec 1, 2020

    @stalkerg

    @thinkingserious this also should fix this issue #409 ;)

  17. stalkerg commented on Jan 13, 2021

    @stalkerg

    Interesting, this approach even formalized!
    https://sans-io.readthedocs.io/
    you definitely should move into this side.

  18. quantology commented on Dec 11, 2021

    @quantology

    Since there aren't any working code examples posted above, I figured I'd share what I just cooked up:

    import aiohttp
    from sendgrid import SendGridAPIClient
    
    async def send_async(client, message):
        if not isinstance(message, dict):
            message = message.get()
        async with aiohttp.ClientSession() as session:
            async with session.post(f"{client.host}/v3/mail/send", 
                                    headers=client._default_headers,
                                    json=message) as resp:
                resp.raise_for_status()
                return await resp.text()
    SendGridAPIClient.send_async = send_async

    Later, I call with: response = await sg_client.send_async(message).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions