We are currently solving the following issue within our repository. Here is the issue text:
--- BEGIN ISSUE ---
Headers from Exceptions
### Is there an existing issue for this?

- [X] I have searched the existing issues

### Describe the bug

Headers set on Exception objects not carried through on all renderers

### Code snippet

```py
raise Unauthorized(
    "Auth required.",
    headers={"foo": "bar"},
)
```

### Expected Behavior

Response should have:

```
Foo: bar
```

### How do you run Sanic?

Sanic CLI

### Operating System

all

### Sanic Version

23.3

### Additional context

_No response_

--- END ISSUE ---

Below are some code segments, each from a relevant file. One or more of these files may contain bugs.
--- BEGIN FILES ---
Path: `sanic/errorpages.py`
Content:
```
1 """
2 Sanic `provides a pattern
3 <https://sanicframework.org/guide/best-practices/exceptions.html#using-sanic-exceptions>`_
4 for providing a response when an exception occurs. However, if you do no handle
5 an exception, it will provide a fallback. There are three fallback types:
6 
7 - HTML - *default*
8 - Text
9 - JSON
10 
11 Setting ``app.config.FALLBACK_ERROR_FORMAT = "auto"`` will enable a switch that
12 will attempt to provide an appropriate response format based upon the
13 request type.
14 """
15 from __future__ import annotations
16 
17 import sys
18 import typing as t
19 
20 from functools import partial
21 from traceback import extract_tb
22 
23 from sanic.exceptions import BadRequest, SanicException
24 from sanic.helpers import STATUS_CODES
25 from sanic.log import deprecation, logger
26 from sanic.pages.error import ErrorPage
27 from sanic.response import html, json, text
28 
29 
30 dumps: t.Callable[..., str]
31 try:
32     from ujson import dumps
33 
34     dumps = partial(dumps, escape_forward_slashes=False)
35 except ImportError:  # noqa
36     from json import dumps
37 
38 if t.TYPE_CHECKING:
39     from sanic import HTTPResponse, Request
40 
41 DEFAULT_FORMAT = "auto"
42 FALLBACK_TEXT = """\
43 The application encountered an unexpected error and could not continue.\
44 """
45 FALLBACK_STATUS = 500
46 JSON = "application/json"
47 
48 
49 class BaseRenderer:
50     """
51     Base class that all renderers must inherit from.
52     """
53 
54     dumps = staticmethod(dumps)
55 
56     def __init__(self, request, exception, debug):
57         self.request = request
58         self.exception = exception
59         self.debug = debug
60 
61     @property
62     def headers(self):
63         if isinstance(self.exception, SanicException):
64             return getattr(self.exception, "headers", {})
65         return {}
66 
67     @property
68     def status(self):
69         if isinstance(self.exception, SanicException):
70             return getattr(self.exception, "status_code", FALLBACK_STATUS)
71         return FALLBACK_STATUS
72 
73     @property
74     def text(self):
75         if self.debug or isinstance(self.exception, SanicException):
76             return str(self.exception)
77         return FALLBACK_TEXT
78 
79     @property
80     def title(self):
81         status_text = STATUS_CODES.get(self.status, b"Error Occurred").decode()
82         return f"{self.status} — {status_text}"
83 
84     def render(self) -> HTTPResponse:
85         """
86         Outputs the exception as a :class:`HTTPResponse`.
87 
88         :return: The formatted exception
89         :rtype: str
90         """
91         output = (
92             self.full
93             if self.debug and not getattr(self.exception, "quiet", False)
94             else self.minimal
95         )
96         return output()
97 
98     def minimal(self) -> HTTPResponse:  # noqa
99         """
100         Provide a formatted message that is meant to not show any sensitive
101         data or details.
102         """
103         raise NotImplementedError
104 
105     def full(self) -> HTTPResponse:  # noqa
106         """
107         Provide a formatted message that has all details and is mean to be used
108         primarily for debugging and non-production environments.
109         """
110         raise NotImplementedError
111 
112 
113 class HTMLRenderer(BaseRenderer):
114     """
115     Render an exception as HTML.
116 
117     The default fallback type.
118     """
119 
120     def full(self) -> HTTPResponse:
121         page = ErrorPage(
122             debug=self.debug,
123             title=super().title,
124             text=super().text,
125             request=self.request,
126             exc=self.exception,
127         )
128         return html(page.render(), status=self.status, headers=self.headers)
129 
130     def minimal(self) -> HTTPResponse:
131         return self.full()
132 
133 
134 class TextRenderer(BaseRenderer):
135     """
136     Render an exception as plain text.
137     """
138 
139     OUTPUT_TEXT = "{title}\n{bar}\n{text}\n\n{body}"
140     SPACER = "  "
141 
142     def full(self) -> HTTPResponse:
143         return text(
144             self.OUTPUT_TEXT.format(
145                 title=self.title,
146                 text=self.text,
147                 bar=("=" * len(self.title)),
148                 body=self._generate_body(full=True),
149             ),
150             status=self.status,
151         )
152 
153     def minimal(self) -> HTTPResponse:
154         return text(
155             self.OUTPUT_TEXT.format(
156                 title=self.title,
157                 text=self.text,
158                 bar=("=" * len(self.title)),
159                 body=self._generate_body(full=False),
160             ),
161             status=self.status,
162             headers=self.headers,
163         )
164 
165     @property
166     def title(self):
167         return f"⚠️ {super().title}"
168 
169     def _generate_body(self, *, full):
170         lines = []
171         if full:
172             _, exc_value, __ = sys.exc_info()
173             exceptions = []
174 
175             lines += [
176                 f"{self.exception.__class__.__name__}: {self.exception} while "
177                 f"handling path {self.request.path}",
178                 f"Traceback of {self.request.app.name} "
179                 "(most recent call last):\n",
180             ]
181 
182             while exc_value:
183                 exceptions.append(self._format_exc(exc_value))
184                 exc_value = exc_value.__cause__
185 
186             lines += exceptions[::-1]
187 
188         for attr, display in (("context", True), ("extra", bool(full))):
189             info = getattr(self.exception, attr, None)
190             if info and display:
191                 lines += self._generate_object_display_list(info, attr)
192 
193         return "\n".join(lines)
194 
195     def _format_exc(self, exc):
196         frames = "\n\n".join(
197             [
198                 f"{self.SPACER * 2}File {frame.filename}, "
199                 f"line {frame.lineno}, in "
200                 f"{frame.name}\n{self.SPACER * 2}{frame.line}"
201                 for frame in extract_tb(exc.__traceback__)
202             ]
203         )
204         return f"{self.SPACER}{exc.__class__.__name__}: {exc}\n{frames}"
205 
206     def _generate_object_display_list(self, obj, descriptor):
207         lines = [f"\n{descriptor.title()}"]
208         for key, value in obj.items():
209             display = self.dumps(value)
210             lines.append(f"{self.SPACER * 2}{key}: {display}")
211         return lines
212 
213 
214 class JSONRenderer(BaseRenderer):
215     """
216     Render an exception as JSON.
217     """
218 
219     def full(self) -> HTTPResponse:
220         output = self._generate_output(full=True)
221         return json(output, status=self.status, dumps=self.dumps)
222 
223     def minimal(self) -> HTTPResponse:
224         output = self._generate_output(full=False)
225         return json(output, status=self.status, dumps=self.dumps)
226 
227     def _generate_output(self, *, full):
228         output = {
229             "description": self.title,
230             "status": self.status,
231             "message": self.text,
232         }
233 
234         for attr, display in (("context", True), ("extra", bool(full))):
235             info = getattr(self.exception, attr, None)
236             if info and display:
237                 output[attr] = info
238 
239         if full:
240             _, exc_value, __ = sys.exc_info()
241             exceptions = []
242 
243             while exc_value:
244                 exceptions.append(
245                     {
246                         "type": exc_value.__class__.__name__,
247                         "exception": str(exc_value),
248                         "frames": [
249                             {
250                                 "file": frame.filename,
251                                 "line": frame.lineno,
252                                 "name": frame.name,
253                                 "src": frame.line,
254                             }
255                             for frame in extract_tb(exc_value.__traceback__)
256                         ],
257                     }
258                 )
259                 exc_value = exc_value.__cause__
260 
261             output["path"] = self.request.path
262             output["args"] = self.request.args
263             output["exceptions"] = exceptions[::-1]
264 
265         return output
266 
267     @property
268     def title(self):
269         return STATUS_CODES.get(self.status, b"Error Occurred").decode()
270 
271 
272 def escape(text):
273     """
274     Minimal HTML escaping, not for attribute values (unlike html.escape).
275     """
276     return f"{text}".replace("&", "&amp;").replace("<", "&lt;")
277 
278 
279 MIME_BY_CONFIG = {
280     "text": "text/plain",
281     "json": "application/json",
282     "html": "text/html",
283 }
284 CONFIG_BY_MIME = {v: k for k, v in MIME_BY_CONFIG.items()}
285 RENDERERS_BY_CONTENT_TYPE = {
286     "text/plain": TextRenderer,
287     "application/json": JSONRenderer,
288     "multipart/form-data": HTMLRenderer,
289     "text/html": HTMLRenderer,
290 }
291 
292 # Handler source code is checked for which response types it returns with the
293 # route error_format="auto" (default) to determine which format to use.
294 RESPONSE_MAPPING = {
295     "json": "json",
296     "text": "text",
297     "html": "html",
298     "JSONResponse": "json",
299     "text/plain": "text",
300     "text/html": "html",
301     "application/json": "json",
302 }
303 
304 
305 def check_error_format(format):
306     if format not in MIME_BY_CONFIG and format != "auto":
307         raise SanicException(f"Unknown format: {format}")
308 
309 
310 def exception_response(
311     request: Request,
312     exception: Exception,
313     debug: bool,
314     fallback: str,
315     base: t.Type[BaseRenderer],
316     renderer: t.Type[t.Optional[BaseRenderer]] = None,
317 ) -> HTTPResponse:
318     """
319     Render a response for the default FALLBACK exception handler.
320     """
321     if not renderer:
322         mt = guess_mime(request, fallback)
323         renderer = RENDERERS_BY_CONTENT_TYPE.get(mt, base)
324 
325     renderer = t.cast(t.Type[BaseRenderer], renderer)
326     return renderer(request, exception, debug).render()
327 
328 
329 def guess_mime(req: Request, fallback: str) -> str:
330     # Attempt to find a suitable MIME format for the response.
331     # Insertion-ordered map of formats["html"] = "source of that suggestion"
332     formats = {}
333     name = ""
334     # Route error_format (by magic from handler code if auto, the default)
335     if req.route:
336         name = req.route.name
337         f = req.route.extra.error_format
338         if f in MIME_BY_CONFIG:
339             formats[f] = name
340 
341     if not formats and fallback in MIME_BY_CONFIG:
342         formats[fallback] = "FALLBACK_ERROR_FORMAT"
343 
344     # If still not known, check for the request for clues of JSON
345     if not formats and fallback == "auto" and req.accept.match(JSON):
346         if JSON in req.accept:  # Literally, not wildcard
347             formats["json"] = "request.accept"
348         elif JSON in req.headers.getone("content-type", ""):
349             formats["json"] = "content-type"
350         # DEPRECATION: Remove this block in 24.3
351         else:
352             c = None
353             try:
354                 c = req.json
355             except BadRequest:
356                 pass
357             if c:
358                 formats["json"] = "request.json"
359                 deprecation(
360                     "Response type was determined by the JSON content of "
361                     "the request. This behavior is deprecated and will be "
362                     "removed in v24.3. Please specify the format either by\n"
363                     f'  error_format="json" on route {name}, by\n'
364                     '  FALLBACK_ERROR_FORMAT = "json", or by adding header\n'
365                     "  accept: application/json to your requests.",
366                     24.3,
367                 )
368 
369     # Any other supported formats
370     if fallback == "auto":
371         for k in MIME_BY_CONFIG:
372             if k not in formats:
373                 formats[k] = "any"
374 
375     mimes = [MIME_BY_CONFIG[k] for k in formats]
376     m = req.accept.match(*mimes)
377     if m:
378         format = CONFIG_BY_MIME[m.mime]
379         source = formats[format]
380         logger.debug(
381             f"The client accepts {m.header}, using '{format}' from {source}"
382         )
383     else:
384         logger.debug(f"No format found, the client accepts {req.accept!r}")
385     return m.mime
386
```
--- END FILES ---

Please first localize the bug based on the issue statement, and then generate a patch according to the `git diff` format fenced by three backticks.

Here is an example:

```diff
diff --git a/examples/server_async.py b/examples/server_async.py
--- a/examples/server_async.py
+++ b/examples/server_async.py
@@ -313,4 +313,4 @@
 
 
 if __name__ == "__main__":
-    asyncio.run(run_async_server("."), debug=True)
+    asyncio.run(run_async_server(), debug=True)
diff --git a/examples/server_sync.py b/examples/server_sync.py
--- a/examples/server_sync.py
+++ b/examples/server_sync.py
@@ -313,5 +313,5 @@
 
 
 if __name__ == "__main__":
-    server = run_sync_server(".")
+    server = run_sync_server()
     server.shutdown()

```

diff --git a/sanic/errorpages.py b/sanic/errorpages.py
--- a/sanic/errorpages.py
+++ b/sanic/errorpages.py
@@ -92,8 +92,10 @@
             self.full
             if self.debug and not getattr(self.exception, "quiet", False)
             else self.minimal
-        )
-        return output()
+        )()
+        output.status = self.status
+        output.headers.update(self.headers)
+        return output
 
     def minimal(self) -> HTTPResponse:  # noqa
         """
@@ -125,7 +127,7 @@
             request=self.request,
             exc=self.exception,
         )
-        return html(page.render(), status=self.status, headers=self.headers)
+        return html(page.render())
 
     def minimal(self) -> HTTPResponse:
         return self.full()
@@ -146,8 +148,7 @@
                 text=self.text,
                 bar=("=" * len(self.title)),
                 body=self._generate_body(full=True),
-            ),
-            status=self.status,
+            )
         )
 
     def minimal(self) -> HTTPResponse:
@@ -157,9 +158,7 @@
                 text=self.text,
                 bar=("=" * len(self.title)),
                 body=self._generate_body(full=False),
-            ),
-            status=self.status,
-            headers=self.headers,
+            )
         )
 
     @property
@@ -218,11 +217,11 @@
 
     def full(self) -> HTTPResponse:
         output = self._generate_output(full=True)
-        return json(output, status=self.status, dumps=self.dumps)
+        return json(output, dumps=self.dumps)
 
     def minimal(self) -> HTTPResponse:
         output = self._generate_output(full=False)
-        return json(output, status=self.status, dumps=self.dumps)
+        return json(output, dumps=self.dumps)
 
     def _generate_output(self, *, full):
         output = {

{"golden_diff": "diff --git a/sanic/errorpages.py b/sanic/errorpages.py\n--- a/sanic/errorpages.py\n+++ b/sanic/errorpages.py\n@@ -92,8 +92,10 @@\n             self.full\n             if self.debug and not getattr(self.exception, \"quiet\", False)\n             else self.minimal\n-        )\n-        return output()\n+        )()\n+        output.status = self.status\n+        output.headers.update(self.headers)\n+        return output\n \n     def minimal(self) -> HTTPResponse:  # noqa\n         \"\"\"\n@@ -125,7 +127,7 @@\n             request=self.request,\n             exc=self.exception,\n         )\n-        return html(page.render(), status=self.status, headers=self.headers)\n+        return html(page.render())\n \n     def minimal(self) -> HTTPResponse:\n         return self.full()\n@@ -146,8 +148,7 @@\n                 text=self.text,\n                 bar=(\"=\" * len(self.title)),\n                 body=self._generate_body(full=True),\n-            ),\n-            status=self.status,\n+            )\n         )\n \n     def minimal(self) -> HTTPResponse:\n@@ -157,9 +158,7 @@\n                 text=self.text,\n                 bar=(\"=\" * len(self.title)),\n                 body=self._generate_body(full=False),\n-            ),\n-            status=self.status,\n-            headers=self.headers,\n+            )\n         )\n \n     @property\n@@ -218,11 +217,11 @@\n \n     def full(self) -> HTTPResponse:\n         output = self._generate_output(full=True)\n-        return json(output, status=self.status, dumps=self.dumps)\n+        return json(output, dumps=self.dumps)\n \n     def minimal(self) -> HTTPResponse:\n         output = self._generate_output(full=False)\n-        return json(output, status=self.status, dumps=self.dumps)\n+        return json(output, dumps=self.dumps)\n \n     def _generate_output(self, *, full):\n         output = {\n", "issue": "Headers from Exceptions\n### Is there an existing issue for this?\n\n- [X] I have searched the existing issues\n\n### Describe the bug\n\nHeaders set on Exception objects not carried through on all renderers\n\n### Code snippet\n\n```py\r\nraise Unauthorized(\r\n    \"Auth required.\",\r\n    headers={\"foo\": \"bar\"},\r\n)\r\n```\n\n### Expected Behavior\n\nResponse should have:\r\n\r\n```\r\nFoo: bar\r\n```\n\n### How do you run Sanic?\n\nSanic CLI\n\n### Operating System\n\nall\n\n### Sanic Version\n\n23.3\n\n### Additional context\n\n_No response_\n", "before_files": [{"content": "\"\"\"\nSanic `provides a pattern\n<https://sanicframework.org/guide/best-practices/exceptions.html#using-sanic-exceptions>`_\nfor providing a response when an exception occurs. However, if you do no handle\nan exception, it will provide a fallback. There are three fallback types:\n\n- HTML - *default*\n- Text\n- JSON\n\nSetting ``app.config.FALLBACK_ERROR_FORMAT = \"auto\"`` will enable a switch that\nwill attempt to provide an appropriate response format based upon the\nrequest type.\n\"\"\"\nfrom __future__ import annotations\n\nimport sys\nimport typing as t\n\nfrom functools import partial\nfrom traceback import extract_tb\n\nfrom sanic.exceptions import BadRequest, SanicException\nfrom sanic.helpers import STATUS_CODES\nfrom sanic.log import deprecation, logger\nfrom sanic.pages.error import ErrorPage\nfrom sanic.response import html, json, text\n\n\ndumps: t.Callable[..., str]\ntry:\n    from ujson import dumps\n\n    dumps = partial(dumps, escape_forward_slashes=False)\nexcept ImportError:  # noqa\n    from json import dumps\n\nif t.TYPE_CHECKING:\n    from sanic import HTTPResponse, Request\n\nDEFAULT_FORMAT = \"auto\"\nFALLBACK_TEXT = \"\"\"\\\nThe application encountered an unexpected error and could not continue.\\\n\"\"\"\nFALLBACK_STATUS = 500\nJSON = \"application/json\"\n\n\nclass BaseRenderer:\n    \"\"\"\n    Base class that all renderers must inherit from.\n    \"\"\"\n\n    dumps = staticmethod(dumps)\n\n    def __init__(self, request, exception, debug):\n        self.request = request\n        self.exception = exception\n        self.debug = debug\n\n    @property\n    def headers(self):\n        if isinstance(self.exception, SanicException):\n            return getattr(self.exception, \"headers\", {})\n        return {}\n\n    @property\n    def status(self):\n        if isinstance(self.exception, SanicException):\n            return getattr(self.exception, \"status_code\", FALLBACK_STATUS)\n        return FALLBACK_STATUS\n\n    @property\n    def text(self):\n        if self.debug or isinstance(self.exception, SanicException):\n            return str(self.exception)\n        return FALLBACK_TEXT\n\n    @property\n    def title(self):\n        status_text = STATUS_CODES.get(self.status, b\"Error Occurred\").decode()\n        return f\"{self.status} \u2014 {status_text}\"\n\n    def render(self) -> HTTPResponse:\n        \"\"\"\n        Outputs the exception as a :class:`HTTPResponse`.\n\n        :return: The formatted exception\n        :rtype: str\n        \"\"\"\n        output = (\n            self.full\n            if self.debug and not getattr(self.exception, \"quiet\", False)\n            else self.minimal\n        )\n        return output()\n\n    def minimal(self) -> HTTPResponse:  # noqa\n        \"\"\"\n        Provide a formatted message that is meant to not show any sensitive\n        data or details.\n        \"\"\"\n        raise NotImplementedError\n\n    def full(self) -> HTTPResponse:  # noqa\n        \"\"\"\n        Provide a formatted message that has all details and is mean to be used\n        primarily for debugging and non-production environments.\n        \"\"\"\n        raise NotImplementedError\n\n\nclass HTMLRenderer(BaseRenderer):\n    \"\"\"\n    Render an exception as HTML.\n\n    The default fallback type.\n    \"\"\"\n\n    def full(self) -> HTTPResponse:\n        page = ErrorPage(\n            debug=self.debug,\n            title=super().title,\n            text=super().text,\n            request=self.request,\n            exc=self.exception,\n        )\n        return html(page.render(), status=self.status, headers=self.headers)\n\n    def minimal(self) -> HTTPResponse:\n        return self.full()\n\n\nclass TextRenderer(BaseRenderer):\n    \"\"\"\n    Render an exception as plain text.\n    \"\"\"\n\n    OUTPUT_TEXT = \"{title}\\n{bar}\\n{text}\\n\\n{body}\"\n    SPACER = \"  \"\n\n    def full(self) -> HTTPResponse:\n        return text(\n            self.OUTPUT_TEXT.format(\n                title=self.title,\n                text=self.text,\n                bar=(\"=\" * len(self.title)),\n                body=self._generate_body(full=True),\n            ),\n            status=self.status,\n        )\n\n    def minimal(self) -> HTTPResponse:\n        return text(\n            self.OUTPUT_TEXT.format(\n                title=self.title,\n                text=self.text,\n                bar=(\"=\" * len(self.title)),\n                body=self._generate_body(full=False),\n            ),\n            status=self.status,\n            headers=self.headers,\n        )\n\n    @property\n    def title(self):\n        return f\"\u26a0\ufe0f {super().title}\"\n\n    def _generate_body(self, *, full):\n        lines = []\n        if full:\n            _, exc_value, __ = sys.exc_info()\n            exceptions = []\n\n            lines += [\n                f\"{self.exception.__class__.__name__}: {self.exception} while \"\n                f\"handling path {self.request.path}\",\n                f\"Traceback of {self.request.app.name} \"\n                \"(most recent call last):\\n\",\n            ]\n\n            while exc_value:\n                exceptions.append(self._format_exc(exc_value))\n                exc_value = exc_value.__cause__\n\n            lines += exceptions[::-1]\n\n        for attr, display in ((\"context\", True), (\"extra\", bool(full))):\n            info = getattr(self.exception, attr, None)\n            if info and display:\n                lines += self._generate_object_display_list(info, attr)\n\n        return \"\\n\".join(lines)\n\n    def _format_exc(self, exc):\n        frames = \"\\n\\n\".join(\n            [\n                f\"{self.SPACER * 2}File {frame.filename}, \"\n                f\"line {frame.lineno}, in \"\n                f\"{frame.name}\\n{self.SPACER * 2}{frame.line}\"\n                for frame in extract_tb(exc.__traceback__)\n            ]\n        )\n        return f\"{self.SPACER}{exc.__class__.__name__}: {exc}\\n{frames}\"\n\n    def _generate_object_display_list(self, obj, descriptor):\n        lines = [f\"\\n{descriptor.title()}\"]\n        for key, value in obj.items():\n            display = self.dumps(value)\n            lines.append(f\"{self.SPACER * 2}{key}: {display}\")\n        return lines\n\n\nclass JSONRenderer(BaseRenderer):\n    \"\"\"\n    Render an exception as JSON.\n    \"\"\"\n\n    def full(self) -> HTTPResponse:\n        output = self._generate_output(full=True)\n        return json(output, status=self.status, dumps=self.dumps)\n\n    def minimal(self) -> HTTPResponse:\n        output = self._generate_output(full=False)\n        return json(output, status=self.status, dumps=self.dumps)\n\n    def _generate_output(self, *, full):\n        output = {\n            \"description\": self.title,\n            \"status\": self.status,\n            \"message\": self.text,\n        }\n\n        for attr, display in ((\"context\", True), (\"extra\", bool(full))):\n            info = getattr(self.exception, attr, None)\n            if info and display:\n                output[attr] = info\n\n        if full:\n            _, exc_value, __ = sys.exc_info()\n            exceptions = []\n\n            while exc_value:\n                exceptions.append(\n                    {\n                        \"type\": exc_value.__class__.__name__,\n                        \"exception\": str(exc_value),\n                        \"frames\": [\n                            {\n                                \"file\": frame.filename,\n                                \"line\": frame.lineno,\n                                \"name\": frame.name,\n                                \"src\": frame.line,\n                            }\n                            for frame in extract_tb(exc_value.__traceback__)\n                        ],\n                    }\n                )\n                exc_value = exc_value.__cause__\n\n            output[\"path\"] = self.request.path\n            output[\"args\"] = self.request.args\n            output[\"exceptions\"] = exceptions[::-1]\n\n        return output\n\n    @property\n    def title(self):\n        return STATUS_CODES.get(self.status, b\"Error Occurred\").decode()\n\n\ndef escape(text):\n    \"\"\"\n    Minimal HTML escaping, not for attribute values (unlike html.escape).\n    \"\"\"\n    return f\"{text}\".replace(\"&\", \"&amp;\").replace(\"<\", \"&lt;\")\n\n\nMIME_BY_CONFIG = {\n    \"text\": \"text/plain\",\n    \"json\": \"application/json\",\n    \"html\": \"text/html\",\n}\nCONFIG_BY_MIME = {v: k for k, v in MIME_BY_CONFIG.items()}\nRENDERERS_BY_CONTENT_TYPE = {\n    \"text/plain\": TextRenderer,\n    \"application/json\": JSONRenderer,\n    \"multipart/form-data\": HTMLRenderer,\n    \"text/html\": HTMLRenderer,\n}\n\n# Handler source code is checked for which response types it returns with the\n# route error_format=\"auto\" (default) to determine which format to use.\nRESPONSE_MAPPING = {\n    \"json\": \"json\",\n    \"text\": \"text\",\n    \"html\": \"html\",\n    \"JSONResponse\": \"json\",\n    \"text/plain\": \"text\",\n    \"text/html\": \"html\",\n    \"application/json\": \"json\",\n}\n\n\ndef check_error_format(format):\n    if format not in MIME_BY_CONFIG and format != \"auto\":\n        raise SanicException(f\"Unknown format: {format}\")\n\n\ndef exception_response(\n    request: Request,\n    exception: Exception,\n    debug: bool,\n    fallback: str,\n    base: t.Type[BaseRenderer],\n    renderer: t.Type[t.Optional[BaseRenderer]] = None,\n) -> HTTPResponse:\n    \"\"\"\n    Render a response for the default FALLBACK exception handler.\n    \"\"\"\n    if not renderer:\n        mt = guess_mime(request, fallback)\n        renderer = RENDERERS_BY_CONTENT_TYPE.get(mt, base)\n\n    renderer = t.cast(t.Type[BaseRenderer], renderer)\n    return renderer(request, exception, debug).render()\n\n\ndef guess_mime(req: Request, fallback: str) -> str:\n    # Attempt to find a suitable MIME format for the response.\n    # Insertion-ordered map of formats[\"html\"] = \"source of that suggestion\"\n    formats = {}\n    name = \"\"\n    # Route error_format (by magic from handler code if auto, the default)\n    if req.route:\n        name = req.route.name\n        f = req.route.extra.error_format\n        if f in MIME_BY_CONFIG:\n            formats[f] = name\n\n    if not formats and fallback in MIME_BY_CONFIG:\n        formats[fallback] = \"FALLBACK_ERROR_FORMAT\"\n\n    # If still not known, check for the request for clues of JSON\n    if not formats and fallback == \"auto\" and req.accept.match(JSON):\n        if JSON in req.accept:  # Literally, not wildcard\n            formats[\"json\"] = \"request.accept\"\n        elif JSON in req.headers.getone(\"content-type\", \"\"):\n            formats[\"json\"] = \"content-type\"\n        # DEPRECATION: Remove this block in 24.3\n        else:\n            c = None\n            try:\n                c = req.json\n            except BadRequest:\n                pass\n            if c:\n                formats[\"json\"] = \"request.json\"\n                deprecation(\n                    \"Response type was determined by the JSON content of \"\n                    \"the request. This behavior is deprecated and will be \"\n                    \"removed in v24.3. Please specify the format either by\\n\"\n                    f'  error_format=\"json\" on route {name}, by\\n'\n                    '  FALLBACK_ERROR_FORMAT = \"json\", or by adding header\\n'\n                    \"  accept: application/json to your requests.\",\n                    24.3,\n                )\n\n    # Any other supported formats\n    if fallback == \"auto\":\n        for k in MIME_BY_CONFIG:\n            if k not in formats:\n                formats[k] = \"any\"\n\n    mimes = [MIME_BY_CONFIG[k] for k in formats]\n    m = req.accept.match(*mimes)\n    if m:\n        format = CONFIG_BY_MIME[m.mime]\n        source = formats[format]\n        logger.debug(\n            f\"The client accepts {m.header}, using '{format}' from {source}\"\n        )\n    else:\n        logger.debug(f\"No format found, the client accepts {req.accept!r}\")\n    return m.mime\n", "path": "sanic/errorpages.py"}], "after_files": [{"content": "\"\"\"\nSanic `provides a pattern\n<https://sanicframework.org/guide/best-practices/exceptions.html#using-sanic-exceptions>`_\nfor providing a response when an exception occurs. However, if you do no handle\nan exception, it will provide a fallback. There are three fallback types:\n\n- HTML - *default*\n- Text\n- JSON\n\nSetting ``app.config.FALLBACK_ERROR_FORMAT = \"auto\"`` will enable a switch that\nwill attempt to provide an appropriate response format based upon the\nrequest type.\n\"\"\"\nfrom __future__ import annotations\n\nimport sys\nimport typing as t\n\nfrom functools import partial\nfrom traceback import extract_tb\n\nfrom sanic.exceptions import BadRequest, SanicException\nfrom sanic.helpers import STATUS_CODES\nfrom sanic.log import deprecation, logger\nfrom sanic.pages.error import ErrorPage\nfrom sanic.response import html, json, text\n\n\ndumps: t.Callable[..., str]\ntry:\n    from ujson import dumps\n\n    dumps = partial(dumps, escape_forward_slashes=False)\nexcept ImportError:  # noqa\n    from json import dumps\n\nif t.TYPE_CHECKING:\n    from sanic import HTTPResponse, Request\n\nDEFAULT_FORMAT = \"auto\"\nFALLBACK_TEXT = \"\"\"\\\nThe application encountered an unexpected error and could not continue.\\\n\"\"\"\nFALLBACK_STATUS = 500\nJSON = \"application/json\"\n\n\nclass BaseRenderer:\n    \"\"\"\n    Base class that all renderers must inherit from.\n    \"\"\"\n\n    dumps = staticmethod(dumps)\n\n    def __init__(self, request, exception, debug):\n        self.request = request\n        self.exception = exception\n        self.debug = debug\n\n    @property\n    def headers(self):\n        if isinstance(self.exception, SanicException):\n            return getattr(self.exception, \"headers\", {})\n        return {}\n\n    @property\n    def status(self):\n        if isinstance(self.exception, SanicException):\n            return getattr(self.exception, \"status_code\", FALLBACK_STATUS)\n        return FALLBACK_STATUS\n\n    @property\n    def text(self):\n        if self.debug or isinstance(self.exception, SanicException):\n            return str(self.exception)\n        return FALLBACK_TEXT\n\n    @property\n    def title(self):\n        status_text = STATUS_CODES.get(self.status, b\"Error Occurred\").decode()\n        return f\"{self.status} \u2014 {status_text}\"\n\n    def render(self) -> HTTPResponse:\n        \"\"\"\n        Outputs the exception as a :class:`HTTPResponse`.\n\n        :return: The formatted exception\n        :rtype: str\n        \"\"\"\n        output = (\n            self.full\n            if self.debug and not getattr(self.exception, \"quiet\", False)\n            else self.minimal\n        )()\n        output.status = self.status\n        output.headers.update(self.headers)\n        return output\n\n    def minimal(self) -> HTTPResponse:  # noqa\n        \"\"\"\n        Provide a formatted message that is meant to not show any sensitive\n        data or details.\n        \"\"\"\n        raise NotImplementedError\n\n    def full(self) -> HTTPResponse:  # noqa\n        \"\"\"\n        Provide a formatted message that has all details and is mean to be used\n        primarily for debugging and non-production environments.\n        \"\"\"\n        raise NotImplementedError\n\n\nclass HTMLRenderer(BaseRenderer):\n    \"\"\"\n    Render an exception as HTML.\n\n    The default fallback type.\n    \"\"\"\n\n    def full(self) -> HTTPResponse:\n        page = ErrorPage(\n            debug=self.debug,\n            title=super().title,\n            text=super().text,\n            request=self.request,\n            exc=self.exception,\n        )\n        return html(page.render())\n\n    def minimal(self) -> HTTPResponse:\n        return self.full()\n\n\nclass TextRenderer(BaseRenderer):\n    \"\"\"\n    Render an exception as plain text.\n    \"\"\"\n\n    OUTPUT_TEXT = \"{title}\\n{bar}\\n{text}\\n\\n{body}\"\n    SPACER = \"  \"\n\n    def full(self) -> HTTPResponse:\n        return text(\n            self.OUTPUT_TEXT.format(\n                title=self.title,\n                text=self.text,\n                bar=(\"=\" * len(self.title)),\n                body=self._generate_body(full=True),\n            )\n        )\n\n    def minimal(self) -> HTTPResponse:\n        return text(\n            self.OUTPUT_TEXT.format(\n                title=self.title,\n                text=self.text,\n                bar=(\"=\" * len(self.title)),\n                body=self._generate_body(full=False),\n            )\n        )\n\n    @property\n    def title(self):\n        return f\"\u26a0\ufe0f {super().title}\"\n\n    def _generate_body(self, *, full):\n        lines = []\n        if full:\n            _, exc_value, __ = sys.exc_info()\n            exceptions = []\n\n            lines += [\n                f\"{self.exception.__class__.__name__}: {self.exception} while \"\n                f\"handling path {self.request.path}\",\n                f\"Traceback of {self.request.app.name} \"\n                \"(most recent call last):\\n\",\n            ]\n\n            while exc_value:\n                exceptions.append(self._format_exc(exc_value))\n                exc_value = exc_value.__cause__\n\n            lines += exceptions[::-1]\n\n        for attr, display in ((\"context\", True), (\"extra\", bool(full))):\n            info = getattr(self.exception, attr, None)\n            if info and display:\n                lines += self._generate_object_display_list(info, attr)\n\n        return \"\\n\".join(lines)\n\n    def _format_exc(self, exc):\n        frames = \"\\n\\n\".join(\n            [\n                f\"{self.SPACER * 2}File {frame.filename}, \"\n                f\"line {frame.lineno}, in \"\n                f\"{frame.name}\\n{self.SPACER * 2}{frame.line}\"\n                for frame in extract_tb(exc.__traceback__)\n            ]\n        )\n        return f\"{self.SPACER}{exc.__class__.__name__}: {exc}\\n{frames}\"\n\n    def _generate_object_display_list(self, obj, descriptor):\n        lines = [f\"\\n{descriptor.title()}\"]\n        for key, value in obj.items():\n            display = self.dumps(value)\n            lines.append(f\"{self.SPACER * 2}{key}: {display}\")\n        return lines\n\n\nclass JSONRenderer(BaseRenderer):\n    \"\"\"\n    Render an exception as JSON.\n    \"\"\"\n\n    def full(self) -> HTTPResponse:\n        output = self._generate_output(full=True)\n        return json(output, dumps=self.dumps)\n\n    def minimal(self) -> HTTPResponse:\n        output = self._generate_output(full=False)\n        return json(output, dumps=self.dumps)\n\n    def _generate_output(self, *, full):\n        output = {\n            \"description\": self.title,\n            \"status\": self.status,\n            \"message\": self.text,\n        }\n\n        for attr, display in ((\"context\", True), (\"extra\", bool(full))):\n            info = getattr(self.exception, attr, None)\n            if info and display:\n                output[attr] = info\n\n        if full:\n            _, exc_value, __ = sys.exc_info()\n            exceptions = []\n\n            while exc_value:\n                exceptions.append(\n                    {\n                        \"type\": exc_value.__class__.__name__,\n                        \"exception\": str(exc_value),\n                        \"frames\": [\n                            {\n                                \"file\": frame.filename,\n                                \"line\": frame.lineno,\n                                \"name\": frame.name,\n                                \"src\": frame.line,\n                            }\n                            for frame in extract_tb(exc_value.__traceback__)\n                        ],\n                    }\n                )\n                exc_value = exc_value.__cause__\n\n            output[\"path\"] = self.request.path\n            output[\"args\"] = self.request.args\n            output[\"exceptions\"] = exceptions[::-1]\n\n        return output\n\n    @property\n    def title(self):\n        return STATUS_CODES.get(self.status, b\"Error Occurred\").decode()\n\n\ndef escape(text):\n    \"\"\"\n    Minimal HTML escaping, not for attribute values (unlike html.escape).\n    \"\"\"\n    return f\"{text}\".replace(\"&\", \"&amp;\").replace(\"<\", \"&lt;\")\n\n\nMIME_BY_CONFIG = {\n    \"text\": \"text/plain\",\n    \"json\": \"application/json\",\n    \"html\": \"text/html\",\n}\nCONFIG_BY_MIME = {v: k for k, v in MIME_BY_CONFIG.items()}\nRENDERERS_BY_CONTENT_TYPE = {\n    \"text/plain\": TextRenderer,\n    \"application/json\": JSONRenderer,\n    \"multipart/form-data\": HTMLRenderer,\n    \"text/html\": HTMLRenderer,\n}\n\n# Handler source code is checked for which response types it returns with the\n# route error_format=\"auto\" (default) to determine which format to use.\nRESPONSE_MAPPING = {\n    \"json\": \"json\",\n    \"text\": \"text\",\n    \"html\": \"html\",\n    \"JSONResponse\": \"json\",\n    \"text/plain\": \"text\",\n    \"text/html\": \"html\",\n    \"application/json\": \"json\",\n}\n\n\ndef check_error_format(format):\n    if format not in MIME_BY_CONFIG and format != \"auto\":\n        raise SanicException(f\"Unknown format: {format}\")\n\n\ndef exception_response(\n    request: Request,\n    exception: Exception,\n    debug: bool,\n    fallback: str,\n    base: t.Type[BaseRenderer],\n    renderer: t.Type[t.Optional[BaseRenderer]] = None,\n) -> HTTPResponse:\n    \"\"\"\n    Render a response for the default FALLBACK exception handler.\n    \"\"\"\n    if not renderer:\n        mt = guess_mime(request, fallback)\n        renderer = RENDERERS_BY_CONTENT_TYPE.get(mt, base)\n\n    renderer = t.cast(t.Type[BaseRenderer], renderer)\n    return renderer(request, exception, debug).render()\n\n\ndef guess_mime(req: Request, fallback: str) -> str:\n    # Attempt to find a suitable MIME format for the response.\n    # Insertion-ordered map of formats[\"html\"] = \"source of that suggestion\"\n    formats = {}\n    name = \"\"\n    # Route error_format (by magic from handler code if auto, the default)\n    if req.route:\n        name = req.route.name\n        f = req.route.extra.error_format\n        if f in MIME_BY_CONFIG:\n            formats[f] = name\n\n    if not formats and fallback in MIME_BY_CONFIG:\n        formats[fallback] = \"FALLBACK_ERROR_FORMAT\"\n\n    # If still not known, check for the request for clues of JSON\n    if not formats and fallback == \"auto\" and req.accept.match(JSON):\n        if JSON in req.accept:  # Literally, not wildcard\n            formats[\"json\"] = \"request.accept\"\n        elif JSON in req.headers.getone(\"content-type\", \"\"):\n            formats[\"json\"] = \"content-type\"\n        # DEPRECATION: Remove this block in 24.3\n        else:\n            c = None\n            try:\n                c = req.json\n            except BadRequest:\n                pass\n            if c:\n                formats[\"json\"] = \"request.json\"\n                deprecation(\n                    \"Response type was determined by the JSON content of \"\n                    \"the request. This behavior is deprecated and will be \"\n                    \"removed in v24.3. Please specify the format either by\\n\"\n                    f'  error_format=\"json\" on route {name}, by\\n'\n                    '  FALLBACK_ERROR_FORMAT = \"json\", or by adding header\\n'\n                    \"  accept: application/json to your requests.\",\n                    24.3,\n                )\n\n    # Any other supported formats\n    if fallback == \"auto\":\n        for k in MIME_BY_CONFIG:\n            if k not in formats:\n                formats[k] = \"any\"\n\n    mimes = [MIME_BY_CONFIG[k] for k in formats]\n    m = req.accept.match(*mimes)\n    if m:\n        format = CONFIG_BY_MIME[m.mime]\n        source = formats[format]\n        logger.debug(\n            f\"The client accepts {m.header}, using '{format}' from {source}\"\n        )\n    else:\n        logger.debug(f\"No format found, the client accepts {req.accept!r}\")\n    return m.mime\n", "path": "sanic/errorpages.py"}]}