Goal
Make backlash.utils.escape handle bytes input on Python 3.
Done when: escape(b'hello') returns 'hello' instead of raising, and a test
covers both the ASCII and non-ASCII bytes cases.
Reproduction (reproduces on Python 3.9 through 3.14):
from backlash.utils import escape
escape(b'hello')
Current behaviour:
TypeError: a bytes-like object is required, not 'str'
Note that non-ASCII bytes work correctly (escape(b'caf\xc3\xa9') returns
'café'), which is why the failure went unnoticed - only the ASCII path is
broken.
Cause: the bytes branch calls s.decode('ascii') purely as a probe and discards
the result, so ASCII input stays bytes and then reaches
s.replace('&', '&') with str arguments. On Python 2 this was harmless
because bytes was str.
Suggested minimal fix: decode unconditionally with
s = s.decode('utf-8', 'replace') and drop the ASCII probe, since the
replacement decode already handles both cases.
Reachable from the interactive console, where a bogus traceback replaces the
real result:
from backlash.console import Console
Console({}, {}, None).eval("import sys; sys.stdout.write(b'hi')")
Why
escape is the single escaping helper used across traceback rendering and
console output, and HTMLStringO.write routes console writes through it. Any
code under debug that writes ASCII bytes to stdout produces a TypeError
traceback instead of its output, which is confusing precisely when the user is
trying to diagnose something else.
Verified as pre-existing rather than a regression: the same reproduction fails
identically on the commit preceding the recent modernization work.
References
backlash/utils.py (escape, the bytes branch)
backlash/tbtools.py (HTMLStringO.write) is the console path that reaches it
tests/test_utils.py may be a natural home for a regression test
Goal
Make
backlash.utils.escapehandlebytesinput on Python 3.Done when:
escape(b'hello')returns'hello'instead of raising, and a testcovers both the ASCII and non-ASCII bytes cases.
Reproduction (reproduces on Python 3.9 through 3.14):
Current behaviour:
Note that non-ASCII bytes work correctly (
escape(b'caf\xc3\xa9')returns'café'), which is why the failure went unnoticed - only the ASCII path isbroken.
Cause: the bytes branch calls
s.decode('ascii')purely as a probe and discardsthe result, so ASCII input stays
bytesand then reachess.replace('&', '&')withstrarguments. On Python 2 this was harmlessbecause
byteswasstr.Suggested minimal fix: decode unconditionally with
s = s.decode('utf-8', 'replace')and drop the ASCII probe, since thereplacement decode already handles both cases.
Reachable from the interactive console, where a bogus traceback replaces the
real result:
Why
escapeis the single escaping helper used across traceback rendering andconsole output, and
HTMLStringO.writeroutes console writes through it. Anycode under debug that writes ASCII bytes to stdout produces a
TypeErrortraceback instead of its output, which is confusing precisely when the user is
trying to diagnose something else.
Verified as pre-existing rather than a regression: the same reproduction fails
identically on the commit preceding the recent modernization work.
References
backlash/utils.py(escape, thebytesbranch)backlash/tbtools.py(HTMLStringO.write) is the console path that reaches ittests/test_utils.pymay be a natural home for a regression test