contextmanager-generator-missing-cleanup / W0135ΒΆ
Message emitted:
The context used in function %r will not be exited.
Description:
Used when a contextmanager is used inside a generator function and the cleanup is not handled.
Problematic code:
import contextlib
@contextlib.contextmanager
def cm():
contextvar = "acquired context"
print("cm enter")
yield contextvar
print("cm exit")
def genfunc_with_cm():
with cm() as context: # [contextmanager-generator-missing-cleanup]
yield context * 2
Correct code:
import contextlib
@contextlib.contextmanager
def good_cm_except():
contextvar = "acquired context"
print("good cm enter")
try:
yield contextvar
except GeneratorExit:
print("good cm exit")
def genfunc_with_cm():
with good_cm_except() as context:
yield context * 2
def genfunc_with_discard():
with good_cm_except():
yield "discarded"
@contextlib.contextmanager
def good_cm_yield_none():
print("good cm enter")
yield
print("good cm exit")
def genfunc_with_none_yield():
with good_cm_yield_none() as var:
print(var)
yield "constant yield"
@contextlib.contextmanager
def good_cm_finally():
contextvar = "acquired context"
print("good cm enter")
try:
yield contextvar
finally:
print("good cm exit")
def good_cm_finally_genfunc():
with good_cm_finally() as context:
yield context * 2
@contextlib.contextmanager
def good_cm_no_cleanup():
contextvar = "acquired context"
print("cm enter")
yield contextvar
def good_cm_no_cleanup_genfunc():
with good_cm_no_cleanup() as context:
yield context * 2
Additional details:
Instantiating and using a contextmanager inside a generator function can result in unexpected behavior if there is an expectation that the context is only available for the generator function. In the case that the generator is not closed or destroyed then the context manager is held suspended as is.
This message warns on the generator function instead of the contextmanager function
because the ways to use a contextmanager are many.
A contextmanager can be used as a decorator (which immediately has __enter__
/__exit__
applied)
and the use of as ...
or discard of the return value also implies whether the context needs cleanup or not.
So for this message, warning the invoker of the contextmanager is important.
The check can create false positives if yield
is used inside an if-else
block without custom cleanup. Use pylint: disable
for these.
from contextlib import contextmanager
@contextmanager
def good_cm_no_cleanup():
contextvar = "acquired context"
print("cm enter")
if some_condition:
yield contextvar
else:
yield contextvar
def good_cm_no_cleanup_genfunc():
# pylint: disable-next=contextmanager-generator-missing-cleanup
with good_cm_no_cleanup() as context:
yield context * 2
Related links:
Created by the basic checker.