diff --git a/django/core/management/base.py b/django/core/management/base.py index ba38ae1748..12b4b42f61 100644 --- a/django/core/management/base.py +++ b/django/core/management/base.py @@ -142,7 +142,7 @@ class DjangoHelpFormatter(HelpFormatter): super().add_arguments(self._reordered_actions(actions)) -class OutputWrapper(TextIOBase): +class OutputWrapper: """ Wrapper around stdout/stderr """ @@ -181,6 +181,9 @@ class OutputWrapper(TextIOBase): self._out.write(style_func(msg)) +TextIOBase.register(OutputWrapper) + + class BaseCommand: """ The base class from which all management commands ultimately diff --git a/tests/user_commands/tests.py b/tests/user_commands/tests.py index 2add272c10..acc338685e 100644 --- a/tests/user_commands/tests.py +++ b/tests/user_commands/tests.py @@ -1,7 +1,7 @@ import os import sys from argparse import ArgumentDefaultsHelpFormatter -from io import StringIO +from io import BytesIO, StringIO, TextIOWrapper from pathlib import Path from unittest import mock @@ -11,6 +11,7 @@ from django.apps import apps from django.core import management from django.core.checks import Tags from django.core.management import BaseCommand, CommandError, find_commands +from django.core.management.base import OutputWrapper from django.core.management.utils import ( find_command, get_random_secret_key, @@ -28,6 +29,29 @@ from .management.commands import dance from .utils import AssertFormatterFailureCaughtContext +class OutputWrapperTests(SimpleTestCase): + def test_unhandled_exceptions(self): + cases = [ + StringIO("Hello world"), + TextIOWrapper(BytesIO(b"Hello world")), + ] + for out in cases: + with self.subTest(out=out): + wrapper = OutputWrapper(out) + out.close() + + unraisable_exceptions = [] + + def unraisablehook(unraisable): + unraisable_exceptions.append(unraisable) + sys.__unraisablehook__(unraisable) + + with mock.patch.object(sys, "unraisablehook", unraisablehook): + del wrapper + + self.assertEqual(unraisable_exceptions, []) + + # A minimal set of apps to avoid system checks running on all apps. @override_settings( INSTALLED_APPS=[