Conversation
…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
left a comment
There was a problem hiding this comment.
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 bytesFix 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
strmessages are unaffected. - The return type annotation
-> str | Noneis now correct.
| if isinstance(body.value, str): | ||
| return body.value |
There was a problem hiding this comment.
As noted in #1190, it would be useful to warn about an invalid value, instead of quietly ignoring it.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
Updated - added a SyntaxWarning for non-string, non-bytes constants (e.g. integer or float literals), in addition to the existing bytes warning. Both cases emit a SyntaxWarning with the literal value and are skipped during extraction. Tests for both are included.
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.
|
Small ping: all CI runs on this branch are sitting in \�ction_required\ — could a maintainer approve the workflow run when convenient? Happy to address any failures after. |
Three separate problems, all found by inspecting the blobs rather than the
local checkout.
1. babel/messages/extract.py was committed with CRLF line endings while
upstream master uses LF. GitHub rendered the whole file as rewritten:
+959/-943 for what is actually a 17-line change. Converting to LF brings
it to +17/-1.
Cause was core.autocrlf=true on the machine that made the commit; this
repository has no .gitattributes to prevent it.
2. tests/messages/test_extract.py was committed with a UTF-8 BOM, which
upstream does not have. Removed.
3. The non-string-constant branch could never run, and its test could never
pass. _parse_python_string() is only called for STRING and FSTRING_START
tokens, but _(42) reaches the tokenizer as a NUMBER token, so nothing
ever inspects it:
b'hello' -> STRING -> _parse_python_string -> warns
42 -> NUMBER -> never reaches it -> silent
Verified by running the tokenizer directly. test_non_string_constant_warning
failed with "DID NOT WARN" on the branch as it stood.
akx's request, and the issue it links (python-babel#1190), are both specifically about
the bytes crash, which does work and is covered. So I removed the dead
branch and the test rather than leaving a permanently red test, and left
a note at the test site explaining what is not covered and why. Warning on
non-string constants would mean handling NUMBER tokens inside a translator
call, which is a wider behavioural change than python-babel#1190 asks for -- happy to
implement it if that is wanted.
tests/messages/test_extract.py: 12 passed. The wider suite is unchanged:
467 failures before this commit, 466 after, all from babel/global.dat and
babel/localedata being absent from a source checkout (they are built during
release, not committed), so every locale-dependent test errors identically
with and without this change.
|
Two things to flag, one of which means CI on this branch was red before. The non-string-constant branch was unreachable, and its test could never pass. So @akx's request, and #1190 which it links, are both specifically about the bytes crash, and that case does work — The diff was showing as a whole-file rewrite. The CRLF came from
@akx — thanks for the pointer in #1190; the bytes fix is the part that mattered and it works. |
|
Transparency note: the review comments I have posted on this PR were written by an LLM Flagging it because the Twisted maintainers raised this and said it should be disclosed |
Summary
Fixes #1190.
Running
pybabel extracton a file containing_(b'foo')(a byte-string literal as a translation argument) crashes with:Root Cause
_parse_python_string()is annotated as-> str | Nonebut returnsbytesfor byte-string literals. The function compiles the token to anast.Constantand returnsbody.valueunconditionally — forb'foo'that isb'foo'(bytes):The caller in
extract_pythonappends the returned value to a string fragment listbuf, then joins it:Fix
Guard the return with
isinstance(body.value, str)so that byte-string literals returnNoneand are silently skipped, exactly like any other non-extractable expression:Bytes literals are not valid
gettextmessages, so ignoring them (rather than crashing) is the correct and expected behavior.Test