This rule raises an issue when an object used as a context manager in a with or async with statement does not implement the corresponding context manager protocol.

Why is this an issue?

The with statement is used to wrap the execution of a block of code with methods defined by a context manager. When entering a with block, Python calls the object’s __enter__ method, and when leaving the block, it calls the object’s __exit__ method. An object can therefore be used in a with statement only if its type implements both of these methods. Because Python looks these methods up on the object’s type and not on the instance, they must be defined on the class itself or inherited from a base class; assigning them as instance attributes does not satisfy the protocol.

The asynchronous variant async with relies on the asynchronous context manager protocol instead: the object’s type must implement both __aenter__ and __aexit__.

Using an object that does not implement the required protocol raises a TypeError at runtime and interrupts the program:

A common mistake is to implement only half of the protocol, for example __enter__ without __exit__, or to mix the synchronous and asynchronous variants.

How to fix it

Make sure the object used in the statement implements the protocol matching the statement:

The contextlib module also provides helpers, such as the @contextmanager and @asynccontextmanager decorators, to build context managers from generator functions.

Code examples

Noncompliant code example

class Resource:
    def __enter__(self):
        return self

with Resource() as r:  # Noncompliant: "Resource" does not implement "__exit__"
    print(r)

Compliant solution

class Resource:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        return False

with Resource() as r:
    print(r)

Noncompliant code example

class Connection:
    def __enter__(self):
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        return False

async def handle():
    async with Connection() as c:  # Noncompliant: "Connection" does not implement the asynchronous context manager protocol
        print(c)

Compliant solution

class Connection:
    async def __aenter__(self):
        return self

    async def __aexit__(self, exc_type, exc_value, traceback):
        return False

async def handle():
    async with Connection() as c:
        print(c)

Resources

Documentation

Related rules