Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Asynchronous context manager

I have an asynchronous API which I'm using to connect and send mail to an SMTP server which has some setup and tear down to it. So it fits nicely into using a contextmanager from Python 3's contextlib.

Though, I don't know if it's possible write because they both use the generator syntax to write.

This might demonstrate the problem (contains a mix of yield-base and async-await syntax to demonstrate the difference between async calls and yields to the context manager).

@contextmanager async def smtp_connection():     client = SMTPAsync()     ...      try:         await client.connect(smtp_url, smtp_port)         await client.starttls()         await client.login(smtp_username, smtp_password)         yield client     finally:         await client.quit() 

Is this kind of thing possible within python currently? and how would I use a with as statement if it is? If not is there a alternative way I could achieve this - maybe using the old style context manager?

like image 882
freebie Avatar asked May 25 '16 09:05

freebie


People also ask

What is async context?

An AsyncContext is created and initialized by a call to ServletRequest#startAsync() or ServletRequest#startAsync(ServletRequest, ServletResponse) . Repeated invocations of these methods will return the same AsyncContext instance, reinitialized as appropriate.

What is Context Manager used for?

The number of database connections that can be opened at a time is also limited(just like file descriptors). Therefore context managers are helpful in managing connections to the database as there could be chances that the programmer may forget to close the connection.

What are context managers in Python?

A context manager is an object that defines a runtime context executing within the with statement.

Is Popen a context manager?

Popen with context managers is documented and was added in Python 3.2.


2 Answers

Since Python 3.7, you can write:

from contextlib import asynccontextmanager  @asynccontextmanager async def smtp_connection():     client = SMTPAsync()     ...      try:         await client.connect(smtp_url, smtp_port)         await client.starttls()         await client.login(smtp_username, smtp_password)         yield client     finally:         await client.quit() 

Before 3.7, you can use the async_generator package for this. On 3.6, you can write:

# This import changed, everything else is the same from async_generator import asynccontextmanager  @asynccontextmanager async def smtp_connection():     client = SMTPAsync()     ...      try:         await client.connect(smtp_url, smtp_port)         await client.starttls()         await client.login(smtp_username, smtp_password)         yield client     finally:         await client.quit() 

And if you want to work all the way back to 3.5, you can write:

# This import changed again: from async_generator import asynccontextmanager, async_generator, yield_  @asynccontextmanager @async_generator      # <-- added this async def smtp_connection():     client = SMTPAsync()     ...      try:         await client.connect(smtp_url, smtp_port)         await client.starttls()         await client.login(smtp_username, smtp_password)         await yield_(client)    # <-- this line changed     finally:         await client.quit() 
like image 155
Nathaniel J. Smith Avatar answered Sep 19 '22 11:09

Nathaniel J. Smith


Thanks to @jonrsharpe was able to make an async context manager.

Here's what mine ended up looking like for anyone who want's some example code:

class SMTPConnection():     def __init__(self, url, port, username, password):         self.client   = SMTPAsync()         self.url      = url         self.port     = port         self.username = username         self.password = password      async def __aenter__(self):         await self.client.connect(self.url, self.port)         await self.client.starttls()         await self.client.login(self.username, self.password)          return self.client      async def __aexit__(self, exc_type, exc, tb):         await self.client.quit() 

usage:

async with SMTPConnection(url, port, username, password) as client:     await client.sendmail(...) 

Feel free to point out if I've done anything stupid.

like image 38
freebie Avatar answered Sep 21 '22 11:09

freebie