-
Notifications
You must be signed in to change notification settings - Fork 716
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
docs: fix asynchronous_mail_send #953
Conversation
The previous solution was still a blocking solution. It had an async function that didn't actually await a future, meaning it would still block the entire event loop. This uses the event loop executor to background it onto an executor thread, which will still at least let IO properly parallelize while the GIL is released.
Crating new threads it's not what you expected from the async approach. |
@stalkerg Some code is difficult to integrate into existing asyncio code because it is already written for blocking IO. Using threading can enable it to run in parallel because it's only waiting on IO anyway, but you can't just use the Python I understand it's not an ideal solution, but the ideal solution would be this library presenting an official async API, which is not a small ask. This is a proper way to integrate it into async code and allow it proper async parallelism in about the best way you can. Either way, creating new threads may not be what you expect from async code, but neither is simply blocking the event loop for the entire IO and not doing any asynchronous operation at all, which is what the previous example actually did. |
# Python 3.6+ compatibility | ||
from asyncio import get_event_loop as get_running_loop | ||
|
||
def async_run(future): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this be within the except? Or top level?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Within the except. asyncio.run
is imported as async_run
, but asyncio.run
only exists for Python 3.7. The version within the except is a rough implementation of asyncio.run
for when it's absent (which would be the case for Python 3.6).
I mostly thought about context switch, GIL, and ThreadPoolExecutor itself.
Yes, but:
But it's the main ask. ) run lib in thread executor or work directly with REST API we can easily without additional explanations. |
@stalkerg In your opinion, do you feel this PR is an improvement over the async guidance that already exists in this library? |
@eshanholtz yes, it's should improve current guidance, because currently, it's looking wrong for python >=3.6. As I understand you can represent Twillio, do you have a plan to prepare the true async/await version? |
I wouldn't call it a hack (it's not nearly clever enough to be a hack; it's just throwing a blocking method into the executor), but it is definitely a temporary workaround. I personally think a good solution is what @stalkerg suggested here in the async ticket, because that will offload the responsibility of managing a good async API out of this library and let the end user use whatever async networking infrastructure they want. It is also easy to refactor the existing library around that plumbing-level API to avoid introducing code duplication while still presenting the same convenient high-level API. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Here are a few more changes based on my local tests:
-
Add this import:
from python_http_client import exceptions
-
Change:
plain_text_content = Content("text/plain", "This is asynchronous sending test.")
html_content = Content("text/html", "<strong>This is asynchronous sending test.</strong>")
to
content = Content("text/plain", "This is asynchronous sending test.")
- Change:
partial(
sendgrid_client.send,
request_body=email))
to
partial(
sendgrid_client.send,
email))
- Change:
except urllib.error.HTTPError as e:
to except exceptions.BadRequestsError as e:
- Add:
print("Note that the response.body will be empty on a 202 status code.")
above
if response.status_code < 300:
print("Email #{} processed".format(n), response.body, response.status_code)
Closing until PR feedback is addressed. |
Fixes
The previous solution was not properly asynchronous despite using an event loop and async functions (as mentioned in this comment on #363 and a few comments in #296). It had an async function that didn't actually await a future, meaning it would still block the entire event loop on every send. This uses the event loop executor to background it onto an executor thread, which will still at least let IO properly parallelize while the GIL is released.
Checklist