Search code examples
pythonimapwith-statementimaplib

Closing a connection with a `with` statement


I want to have a class that represents an IMAP connection and use it with a with statement as follows:

class IMAPConnection:
    def __enter__(self):
        connection = imaplib.IMAP4_SSL(IMAP_HOST)

        try:
            connection.login(MAIL_USERNAME, MAIL_PASS)
        except imaplib.IMAP4.error:
            log.error('Failed to log in')

        return connection

    def __exit__(self, type, value, traceback):
        self.close()

with IMAPConnection() as c:
    rv, data = c.list()
    print(rv, data)

Naturally this fails since IMAPConnections has no attribute close. How can I store the connection and pass it to the __exit__ function when the with statement is finished?


Solution

  • You need to store connection in object attributes. Something like this:

    class IMAPConnection:
        def __enter__(self):
            self.connection = imaplib.IMAP4_SSL(IMAP_HOST)
    
            try:
                self.connection.login(MAIL_USERNAME, MAIL_PASS)
            except imaplib.IMAP4.error:
                log.error('Failed to log in')
    
            return self.connection
    
        def __exit__(self, type, value, traceback):
            self.connection.close()
    

    You would also want to implement list method for you class.

    Edit: I just now realized what your actual problem is. when you do with SomeClass(*args, **kwargs) as c c is not a value returned by __enter__ method. c is instance of SomeClass. That's a root of your problem you returned connection from __enter__ and assumed that c is said connection.