From ec0e784f91b551c654f0962431cc31091926792d Mon Sep 17 00:00:00 2001
From: Adam Johnson <me@adamj.eu>
Date: Sat, 4 Jan 2025 03:29:53 +0000
Subject: [PATCH] Fixed #36056 -- Made OutputWrapper a virtual subclass of
 TextIOBase.

This fixes the ignored exception in self._out.flush() from
django.core.management.base.OutputWrapper:
`ValueError: I/O operation on closed file.`
---
 django/core/management/base.py |  5 ++++-
 tests/user_commands/tests.py   | 26 +++++++++++++++++++++++++-
 2 files changed, 29 insertions(+), 2 deletions(-)

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=[