Skip to content

fix(extract): skip bytes literals in _parse_python_string (#1190) - #1298

Open
Mukller wants to merge 2 commits into
python-babel:masterfrom
Mukller:fix/extract-python-bytes-literal-crash
Open

fix(extract): skip bytes literals in _parse_python_string (#1190)#1298
Mukller wants to merge 2 commits into
python-babel:masterfrom
Mukller:fix/extract-python-bytes-literal-crash

Conversation

@Mukller

@Mukller Mukller commented Jul 29, 2026

Copy link
Copy Markdown

Summary

Fixes #1190.

Running pybabel extract on a file containing _(b'foo') (a byte-string literal as a translation argument) crashes with:

TypeError: sequence item 0: expected str instance, bytes found

Root Cause

_parse_python_string() is annotated as -> str | None but returns bytes for byte-string literals. The function compiles the token to an ast.Constant and returns body.value unconditionally — for b'foo' that is b'foo' (bytes):

# _parse_python_string — before fix
if isinstance(body, ast.Constant):
    return body.value   # returns bytes for b'foo' — wrong!

The caller in extract_python appends the returned value to a string fragment list buf, then joins it:

buf.append(val)                  # buf = [b'foo']
messages.append(''.join(buf))    # TypeError!

Fix

Guard the return with isinstance(body.value, str) so that byte-string literals return None and are silently skipped, exactly like any other non-extractable expression:

         if isinstance(body, ast.Constant):
-            return body.value
+            if isinstance(body.value, str):
+                return body.value

Bytes literals are not valid gettext messages, so ignoring them (rather than crashing) is the correct and expected behavior.

Test

# test.py
_(b'foo')  # should be silently ignored
_('bar')   # should be extracted normally
$ pybabel extract test.py -o messages.pot
# Before: TypeError crash
# After:  Only 'bar' extracted; b'foo' silently ignored

…el#1190)

_parse_python_string() is typed -> str | None but was returning bytes
for literals like b'foo'. The caller appended the bytes value to `buf`
and the subsequent ''.join(buf) raised:
    TypeError: sequence item 0: expected str instance, bytes found

Guard the return with isinstance(body.value, str) so that byte-string
literals are treated the same as other non-extractable expressions
(i.e. silently ignored). bytes cannot be used as gettext messages anyway.

@Mukller Mukller left a comment

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

Bug Trace

# source.py: _(b'foo')

# tokenize produces: tok=STRING, value="b'foo'"

# _parse_python_string("b'foo'", encoding='UTF-8', future_flags=0)
#   compile("b'foo'", '<string>', 'eval', ...) -> ast.Expression
#   body = ast.Constant(value=b'foo')   # bytes!
#   isinstance(body, ast.Constant) -> True
#   return body.value                   # returns b'foo' (bytes)

# Back in extract_python:
#   val = b'foo'    (not None, so not skipped)
#   buf.append(b'foo')       # buf = [b'foo']
#   ''.join(buf)             # TypeError: expected str, got bytes

Fix Correctness

# After fix:
if isinstance(body, ast.Constant):
    if isinstance(body.value, str):   # new guard
        return body.value
# bytes falls through to implicit 'return None'
Token body.value type Before After
'foo' str Returns 'foo' Returns 'foo'
b'foo' bytes Returns b'foo' → crash ✗ Returns None → skipped ✓
42 int Returns 42 → crash ✗ Returns None → skipped ✓
3.14 float Returns 3.14 → crash ✗ Returns None → skipped ✓

Note: integer and float ast.Constant values would cause the same crash — this guard also fixes those edge cases.

Scope

  • 2 lines changed inside _parse_python_string().
  • No new imports needed.
  • Extracted str messages are unaffected.
  • The return type annotation -> str | None is now correct.

Comment thread babel/messages/extract.py
Comment on lines +713 to +714
if isinstance(body.value, str):
return body.value

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As noted in #1190, it would be useful to warn about an invalid value, instead of quietly ignoring it.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done — added a SyntaxWarning in the latest commit. The warning includes the literal value and a note to use a str literal instead:

SyntaxWarning: Bytes literal b'foo' passed to a gettext function; it will be skipped during message extraction. Use a str literal instead.

Instead of silently returning None, emit a SyntaxWarning so users know
their _(b"...") call is being skipped during extraction.

Addresses review feedback from @akx on PR python-babel#1298.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

pybabel extract crashes when it encounters a byte string

2 participants