# Buggy code
def flowdetails(state, flow: mitmproxy.flow.Flow):
    text = []

    sc = flow.server_conn
    cc = flow.client_conn
    req: typing.Optional[http.HTTPRequest]
    resp: typing.Optional[http.HTTPResponse]
    if isinstance(flow, http.HTTPFlow):
        req = flow.request
        resp = flow.response
    else:
        req = None
        resp = None
    metadata = flow.metadata

    if metadata is not None and len(metadata) > 0:
        parts = [(str(k), repr(v)) for k, v in metadata.items()]
        text.append(urwid.Text([("head", "Metadata:")]))
        text.extend(common.format_keyvals(parts, indent=4))

    if sc is not None and sc.peername:
        text.append(urwid.Text([("head", "Server Connection:")]))
        parts = [
            ("Address", human.format_address(sc.address)),
        ]
        if sc.peername:
            parts.append(("Resolved Address", human.format_address(sc.peername)))
        if resp:
            parts.append(("HTTP Version", resp.http_version))
        if sc.alpn:
            parts.append(("ALPN", strutils.bytes_to_escaped_str(sc.alpn)))

        text.extend(common.format_keyvals(parts, indent=4))

        c = sc.certificate_list[0]
        if c:
            text.append(urwid.Text([("head", "Server Certificate:")]))
            parts = [
                ("Type", "%s, %s bits" % c.keyinfo),
                ("SHA256 digest", c.fingerprint().hex()),
                ("Valid to", str(c.notafter)),
                ("Valid from", str(c.notbefore)),
                ("Serial", str(c.serial)),
                (
                    "Subject",
                    urwid.Pile(
                        common.format_keyvals(c.subject, key_format="highlight")
                    ),
                ),
                (
                    "Issuer",
                    urwid.Pile(common.format_keyvals(c.issuer, key_format="highlight")),
                ),
            ]

            if c.altnames:
                parts.append(
                    (
                        "Alt names",
                        ", ".join(strutils.bytes_to_escaped_str(x) for x in c.altnames),
                    )
                )
            text.extend(common.format_keyvals(parts, indent=4))

    if cc is not None:
        text.append(urwid.Text([("head", "Client Connection:")]))

        parts = [
            ("Address", human.format_address(cc.peername)),
        ]
        if req:
            parts.append(("HTTP Version", req.http_version))
        if cc.tls_version:
            parts.append(("TLS Version", cc.tls_version))
        if cc.sni:
            parts.append(("Server Name Indication", cc.sni))
        if cc.cipher:
            parts.append(("Cipher Name", cc.cipher))
        if cc.alpn:
            parts.append(("ALPN", strutils.bytes_to_escaped_str(cc.alpn)))

        text.extend(common.format_keyvals(parts, indent=4))

    parts = []

    if cc is not None and cc.timestamp_start:
        parts.append(
            ("Client conn. established", maybe_timestamp(cc, "timestamp_start"))
        )
        if cc.tls_established:
            parts.append(
                (
                    "Client conn. TLS handshake",
                    maybe_timestamp(cc, "timestamp_tls_setup"),
                )
            )
        parts.append(("Client conn. closed", maybe_timestamp(cc, "timestamp_end")))

    if sc is not None and sc.timestamp_start:
        parts.append(("Server conn. initiated", maybe_timestamp(sc, "timestamp_start")))
        parts.append(
            ("Server conn. TCP handshake", maybe_timestamp(sc, "timestamp_tcp_setup"))
        )
        if sc.tls_established:
            parts.append(
                (
                    "Server conn. TLS handshake",
                    maybe_timestamp(sc, "timestamp_tls_setup"),
                )
            )
        parts.append(("Server conn. closed", maybe_timestamp(sc, "timestamp_end")))

    if req is not None and req.timestamp_start:
        parts.append(("First request byte", maybe_timestamp(req, "timestamp_start")))
        parts.append(("Request complete", maybe_timestamp(req, "timestamp_end")))

    if resp is not None and resp.timestamp_start:
        parts.append(("First response byte", maybe_timestamp(resp, "timestamp_start")))
        parts.append(("Response complete", maybe_timestamp(resp, "timestamp_end")))

    if parts:
        # sort operations by timestamp
        parts = sorted(parts, key=lambda p: p[1])

        text.append(urwid.Text([("head", "Timing:")]))
        text.extend(common.format_keyvals(parts, indent=4))

    return searchable.Searchable(text)


# Traceback (ValueError)
Traceback (most recent call last):
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/master.py", line 56, in run_loop
loop()
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/main_loop.py", line 287, in run
self._run()
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/main_loop.py", line 385, in _run
self.event_loop.run()
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/main_loop.py", line 1494, in run
reraise(*exc_info)
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/compat.py", line 58, in reraise
raise value
File "/usr/local/Cellar/python@3.9/3.9.0_1/Frameworks/Python.framework/Versions/3.9/lib/python3.9/asyncio/events.py", line 80, in _run
self._context.run(self._callback, *self._args)
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/raw_display.py", line 416, in <lambda>
wrapper = lambda: self.parse_input(
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/raw_display.py", line 515, in parse_input
callback(processed, processed_codes)
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/main_loop.py", line 412, in _update
self.process_input(keys)
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/main_loop.py", line 513, in process_input
k = self._topmost_widget.keypress(self.screen_size, k)
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/window.py", line 310, in keypress
k = super().keypress(size, k)
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/container.py", line 1135, in keypress
return self.body.keypress( (maxcol, remaining), key )
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/window.py", line 44, in keypress
ret = super().keypress(size, key)
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/container.py", line 1135, in keypress
return self.body.keypress( (maxcol, remaining), key )
File "/Users/alex/work/code/python/mitmproxy/venv/lib/python3.9/site-packages/urwid/container.py", line 1135, in keypress
return self.body.keypress( (maxcol, remaining), key )
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/tabs.py", line 41, in keypress
self.change_tab((self.tab_offset + 1) % n)
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/tabs.py", line 34, in change_tab
self.show()
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/tabs.py", line 73, in show
body = self.tabs[self.tab_offset % len(self.tabs)][1](),
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/flowview.py", line 176, in view_details
return flowdetailview.flowdetails(self.view, self.flow)
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/flowdetailview.py", line 72, in flowdetails
parts.append(("Alt names", ", ".join(strutils.bytes_to_escaped_str(x) for x in c.altnames)))
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/tools/console/flowdetailview.py", line 72, in <genexpr>
parts.append(("Alt names", ", ".join(strutils.bytes_to_escaped_str(x) for x in c.altnames)))
File "/Users/alex/work/code/python/mitmproxy/mitmproxy/utils/strutils.py", line 95, in bytes_to_escaped_str
raise ValueError(f"data must be bytes, but is {data.__class__.__name__}")
ValueError: data must be bytes, but is str

mitmproxy has crashed!

# Fixed code
def flowdetails(state, flow: mitmproxy.flow.Flow):
    text = []

    sc = flow.server_conn
    cc = flow.client_conn
    req: typing.Optional[http.HTTPRequest]
    resp: typing.Optional[http.HTTPResponse]
    if isinstance(flow, http.HTTPFlow):
        req = flow.request
        resp = flow.response
    else:
        req = None
        resp = None
    metadata = flow.metadata

    if metadata is not None and len(metadata) > 0:
        parts = [(str(k), repr(v)) for k, v in metadata.items()]
        text.append(urwid.Text([("head", "Metadata:")]))
        text.extend(common.format_keyvals(parts, indent=4))

    if sc is not None and sc.peername:
        text.append(urwid.Text([("head", "Server Connection:")]))
        parts = [
            ("Address", human.format_address(sc.address)),
        ]
        if sc.peername:
            parts.append(("Resolved Address", human.format_address(sc.peername)))
        if resp:
            parts.append(("HTTP Version", resp.http_version))
        if sc.alpn:
            parts.append(("ALPN", strutils.bytes_to_escaped_str(sc.alpn)))

        text.extend(common.format_keyvals(parts, indent=4))

        c = sc.certificate_list[0]
        if c:
            text.append(urwid.Text([("head", "Server Certificate:")]))
            parts = [
                ("Type", "%s, %s bits" % c.keyinfo),
                ("SHA256 digest", c.fingerprint().hex()),
                ("Valid to", str(c.notafter)),
                ("Valid from", str(c.notbefore)),
                ("Serial", str(c.serial)),
                (
                    "Subject",
                    urwid.Pile(
                        common.format_keyvals(c.subject, key_format="highlight")
                    ),
                ),
                (
                    "Issuer",
                    urwid.Pile(common.format_keyvals(c.issuer, key_format="highlight")),
                ),
            ]

            if c.altnames:
                parts.append(("Alt names", ", ".join(c.altnames)))
            text.extend(common.format_keyvals(parts, indent=4))

    if cc is not None:
        text.append(urwid.Text([("head", "Client Connection:")]))

        parts = [
            ("Address", human.format_address(cc.peername)),
        ]
        if req:
            parts.append(("HTTP Version", req.http_version))
        if cc.tls_version:
            parts.append(("TLS Version", cc.tls_version))
        if cc.sni:
            parts.append(("Server Name Indication", cc.sni))
        if cc.cipher:
            parts.append(("Cipher Name", cc.cipher))
        if cc.alpn:
            parts.append(("ALPN", strutils.bytes_to_escaped_str(cc.alpn)))

        text.extend(common.format_keyvals(parts, indent=4))

    parts = []

    if cc is not None and cc.timestamp_start:
        parts.append(
            ("Client conn. established", maybe_timestamp(cc, "timestamp_start"))
        )
        if cc.tls_established:
            parts.append(
                (
                    "Client conn. TLS handshake",
                    maybe_timestamp(cc, "timestamp_tls_setup"),
                )
            )
        parts.append(("Client conn. closed", maybe_timestamp(cc, "timestamp_end")))

    if sc is not None and sc.timestamp_start:
        parts.append(("Server conn. initiated", maybe_timestamp(sc, "timestamp_start")))
        parts.append(
            ("Server conn. TCP handshake", maybe_timestamp(sc, "timestamp_tcp_setup"))
        )
        if sc.tls_established:
            parts.append(
                (
                    "Server conn. TLS handshake",
                    maybe_timestamp(sc, "timestamp_tls_setup"),
                )
            )
        parts.append(("Server conn. closed", maybe_timestamp(sc, "timestamp_end")))

    if req is not None and req.timestamp_start:
        parts.append(("First request byte", maybe_timestamp(req, "timestamp_start")))
        parts.append(("Request complete", maybe_timestamp(req, "timestamp_end")))

    if resp is not None and resp.timestamp_start:
        parts.append(("First response byte", maybe_timestamp(resp, "timestamp_start")))
        parts.append(("Response complete", maybe_timestamp(resp, "timestamp_end")))

    if parts:
        # sort operations by timestamp
        parts = sorted(parts, key=lambda p: p[1])

        text.append(urwid.Text([("head", "Timing:")]))
        text.extend(common.format_keyvals(parts, indent=4))

    return searchable.Searchable(text)