import asyncio
+import inspect
import traceback
import typing
from starlette.types import ASGIApp, Message, Receive, Scope, Send
STYLES = """
+p {
+ color: #211c1c;
+}
.traceback-container {
border: 1px solid #038BB8;
}
font-size: 20px;
margin-top: 0px;
}
-.traceback-content {
- padding: 5px 0px 20px 20px;
-}
.frame-line {
+ padding-left: 10px;
+}
+.center-line {
+ background-color: #038BB8;
+ color: #f9f6e1;
+ padding: 5px 0px 5px 5px;
+}
+.lineno {
+ margin-right: 5px;
+}
+.frame-filename {
font-weight: unset;
- padding: 10px 10px 10px 20px;
+ padding: 10px 10px 10px 0px;
background-color: #E4F4FD;
- margin-left: 10px;
margin-right: 10px;
font: #394D54;
color: #191f21;
font-size: 17px;
border: 1px solid #c7dce8;
}
+.collapse-btn {
+ float: right;
+ padding: 0px 5px 1px 5px;
+ border: solid 1px #96aebb;
+ cursor: pointer;
+}
+.collapsed {
+ display: none;
+}
+"""
+
+JS = """
+<script type="text/javascript">
+ function collapse(element){
+ const frameId = element.getAttribute("data-frame-id");
+ const frame = document.getElementById(frameId);
+
+ if (frame.classList.contains("collapsed")){
+ element.innerHTML = "‒";
+ frame.classList.remove("collapsed");
+ } else {
+ element.innerHTML = "+";
+ frame.classList.add("collapsed");
+ }
+ }
+</script>
"""
TEMPLATE = """
<body>
<h1>500 Server Error</h1>
<h2>{error}</h2>
- <div class='traceback-container'>
- <p class='traceback-title'>Traceback</p>
- <div class='traceback-content'>{exc_html}</div>
+ <div class="traceback-container">
+ <p class="traceback-title">Traceback</p>
+ <div>{exc_html}</div>
</div>
+ {js}
</body>
</html>
"""
FRAME_TEMPLATE = """
<div>
- File <span class='debug-filename'>`{frame_filename}`</span>,
+ <p class="frame-filename"><span class="debug-filename frame-line">File {frame_filename}</span>,
line <i>{frame_lineno}</i>,
in <b>{frame_name}</b>
- <p class='frame-line'>{frame_line}</p>
+ <span class="collapse-btn" data-frame-id="{frame_filename}-{frame_lineno}" onclick="collapse(this)">‒</span>
+ </p>
+ <div id="{frame_filename}-{frame_lineno}">{code_context}</div>
</div>
"""
+LINE = """
+<p><span class="frame-line">
+<span class="lineno">{lineno}.</span> {line}</span></p>
+"""
+
+CENTER_LINE = """
+<p class="center-line"><span class="frame-line center-line">
+<span class="lineno">{lineno}.</span> {line}</span></p>
+"""
+
class ServerErrorMiddleware:
"""
# to optionally raise the error within the test case.
raise exc from None
- def generate_frame_html(self, frame: traceback.FrameSummary) -> str:
+ def format_line(
+ self, position: int, line: str, frame_lineno: int, center_lineno: int
+ ) -> str:
+ values = {
+ "line": line.replace(" ", " "),
+ "lineno": frame_lineno + (position - center_lineno),
+ }
+
+ if position != center_lineno:
+ return LINE.format(**values)
+ return CENTER_LINE.format(**values)
+
+ def generate_frame_html(self, frame: inspect.FrameInfo, center_lineno: int) -> str:
+ code_context = "".join(
+ self.format_line(context_position, line, frame.lineno, center_lineno)
+ for context_position, line in enumerate(frame.code_context)
+ )
+
values = {
"frame_filename": frame.filename,
"frame_lineno": frame.lineno,
- "frame_name": frame.name,
- "frame_line": frame.line,
+ "frame_name": frame.function,
+ "code_context": code_context,
}
return FRAME_TEMPLATE.format(**values)
- def generate_html(self, exc: Exception) -> str:
+ def generate_html(self, exc: Exception, limit: int = 7) -> str:
traceback_obj = traceback.TracebackException.from_exception(
exc, capture_locals=True
)
+ frames = inspect.getinnerframes(
+ traceback_obj.exc_traceback, limit # type: ignore
+ )
+
+ center_lineno = int((limit - 1) / 2)
exc_html = "".join(
- self.generate_frame_html(frame) for frame in traceback_obj.stack
+ self.generate_frame_html(frame, center_lineno) for frame in reversed(frames)
)
error = f"{traceback_obj.exc_type.__name__}: {traceback_obj}"
- return TEMPLATE.format(styles=STYLES, error=error, exc_html=exc_html)
+ return TEMPLATE.format(styles=STYLES, js=JS, error=error, exc_html=exc_html)
def generate_plain_text(self, exc: Exception) -> str:
return "".join(traceback.format_tb(exc.__traceback__))