Django: silence “Exception ignored in ... OutputWrapper”

You might see this message when running pytest on your Django project:
$ pytest
...
Exception ignored in: <django.core.management.base.OutputWrapper object at 0x108576170>
Traceback (most recent call last):
File "/.../django/core/management/base.py", line 171, in flush
self._out.flush()
ValueError: I/O operation on closed file.
The message doesn’t fail tests but reports an unraisable exception that occurred inside Django. This is an exception at a point that Python cannot crash the program. It’s triggered by a bug in Django’s OutputWrapper class, which is used to wrap output in management commands when used in combination with pytest’s output capturing.
The message can appear twice, once for sys.stdout and once for sys.stderr, for each management command tested for an exception, like:
with pytest.raises(CommandError) as excinfo:
call_command(...)
This message is always visible on Python 3.13+, but it can also appear on older versions when using Python’s development mode, like:
$ python -X dev -m pytest
(I recommend using development mode locally and on CI; it activates several useful features!)
I reported and fixed the underlying issue in Ticket #36056. The commit is scheduled for release in Django 5.2 and will not be backported to older versions. So, until 5.2 is out, your test output can get cluttered with these messages.
Below is a fix that wraps the default sys.unraisablehook to silence these exceptions. Add the code to your root conftest.py—no need to wrap it within any pytest hook function.
import sys
import django
from django.core.management.base import OutputWrapper
# Silence “Exception ignored in ... OutputWrapper”:
# ValueError: I/O operation on closed file.
# https://adamj.eu/tech/2025/01/08/django-silence-exception-ignored-outputwrapper/
# https://code.djangoproject.com/ticket/36056
if django.VERSION < (5, 2):
orig_unraisablehook = sys.unraisablehook
def unraisablehook(unraisable):
if (
unraisable.exc_type is ValueError
and unraisable.exc_value is not None
and unraisable.exc_value.args == ("I/O operation on closed file.",)
and isinstance(unraisable.object, OutputWrapper)
):
return
orig_unraisablehook(unraisable)
sys.unraisablehook = unraisablehook
The fix works by detecting the specific unraisable exception and returning early, or otherwise calling the original hook. The fix is wrapped with a Django 5.2 version check, so it won’t run after you upgrade to the fixed version. If you use my tool django-upgrade, it will automatically remove the whole if statement later when you target it to Django 5.2+.
😸😸😸 Check out my new book on using GitHub effectively, Boost Your GitHub DX! 😸😸😸
One summary email a week, no spam, I pinky promise.
Related posts:
- Python: debug unraisable exceptions with Rich
- Python: test for unraisable exceptions with
unittest.mock
Tags: django