-
Notifications
You must be signed in to change notification settings - Fork 722
Asyncio example is completely wrong, update #988
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Comments
Hello @agronick, Thank you for raising this issue! 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. Here are some related issues and PRs to consider upon update:
With best regards, Elmer |
#953 is exactly what I'm talking about but it was closed. |
upvote. unfortunately current SDK is not compatible with asyncio which makes it unusable with such things like aiohttp or fastapi |
Hi, it's 2024 and the example is still wrong. Open to PRs? I can help It's as simple as wrapping the call using asyncio.to_thread so it doesn't block the main thread. Ideally though the SDK itself would provide support for asyncio, maybe through a transport layer that uses aiohttp or httpx |
I'll just use the api lol |
The async example never actually worked in an async way. It tricked users into thinking they were using async code but in reality the sendgrid_client.send call (which is what was waiting on I/O) was still blocking. In addition, asyncio.async (used in the example) was deprecated in Python 3.7 and removed in Python 3.10.
@agronick is right. The async example is wrong and misleading for less experienced developers. And I agree with @urbanonymous that it's best using the API directly. # example.py
from abc import ABC, abstractmethod
from dataclasses import dataclass
# TODO: use Pydantic for the Email object.
@dataclass
class Email:
recipients: list[str]
sender: str
sender_name: str
subject: str
body: str
cc: list[str] = None
bcc: list[str] = None
class EmailHandler(ABC): # interface
@abstractmethod
async def send(self, email: Email) -> None:
pass
# ----------------------------------------------
# SendGrid implementation of EmailHandler
# ----------------------------------------------
import httpx
class SendGridClient(EmailHandler):
def __init__(self, api_key: str, http_client: httpx.AsyncClient):
if not api_key:
raise ValueError("API key is required")
self.http_client = http_client
self.api_key = api_key
async def send(self, email: Email) -> None:
response = await self.http_client.post(
"https://api.sendgrid.com/v3/mail/send",
headers={
"Authorization": f"Bearer {self.api_key}",
"Content-Type": "application/json",
},
json=self.get_body(email),
)
# Note: in case of error, inspect response.text
response.raise_for_status() # Raise an error for bad responses
def get_body(self, email: Email) -> dict:
return {
"personalizations": [
{
"to": [{"email": recipient} for recipient in email.recipients],
"subject": email.subject,
"cc": [{"email": cc} for cc in email.cc] if email.cc else None,
"bcc": [{"email": bcc} for bcc in email.bcc] if email.bcc else None,
}
],
"from": {"email": email.sender, "name": email.sender_name},
"content": [{"type": "text/html", "value": email.body}],
}
# ----------------------------------------------
# Test
# ----------------------------------------------
import os
async def example():
async with httpx.AsyncClient() as http_client:
sendgrid = SendGridClient(
api_key=os.environ.get("SENDGRID_API_KEY"), http_client=http_client
)
await sendgrid.send(
Email(
recipients=["[email protected]"],
sender="[email protected]",
sender_name="Example",
subject="Test email",
body="<h1>Hello</h1>",
)
)
if __name__ == "__main__":
import asyncio
asyncio.run(example()) PowerShell: $env:SENDGRID_API_KEY="<YOUR_API_KEY>"
python example.py Bash: SENDGRID_API_KEY="<YOUR_API_KEY>" python example.py Tip Do not instantiate a new Tip To understand why the example in this repository is wrong, watch this: Ryan Dahl: Original Node.js presentation. |
Issue Summary
I'm talking about this example https://github.com./sendgrid/sendgrid-python/blob/main/use_cases/asynchronous_mail_send.md
Steps to Reproduce
Using asyncio is more than just putting
async
in front of the function doing the http request. In fact this is the completely wrong way to useasyncio
and will actually destroys performance as this blocks the event loop from doing any other work while the http request is being made.To use asyncio correctly the tcp socket sending the data must be using asyncio and one of the low level asyncio socket functions. https://docs.python.org/3/library/asyncio-stream.html These are awaited, allowing the event loop to do other work.
What you have suggested is EXTREMELY bad practice and will stall the servers of anyone who uses it. This is why you can't use
requests
orurllib
with asyncio. You have to use something like aiohttp.The right way to use asyncio with your library is to run it in a thread pool executor so that the blocking IO stays off the main thread.
You would be better off deleting that example than keeping it in it's current form.
The text was updated successfully, but these errors were encountered: