Skip to content

Commit 7a8b587

Browse files
committed
Avoid hot isinstanceing in PO file parse loop
1 parent 8333449 commit 7a8b587

File tree

2 files changed

+21
-5
lines changed

2 files changed

+21
-5
lines changed

babel/messages/pofile.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -300,16 +300,25 @@ def _process_comment(self, line) -> None:
300300

301301
def parse(self, fileobj: IO[AnyStr] | Iterable[AnyStr]) -> None:
302302
"""
303-
Reads from the file-like object `fileobj` and adds any po file
304-
units found in it to the `Catalog` supplied to the constructor.
303+
Reads from the file-like object (or iterable of string-likes) `fileobj`
304+
and adds any po file units found in it to the `Catalog`
305+
supplied to the constructor.
306+
307+
All of the items in the iterable must be the same type; either `str`
308+
or `bytes` (decoded with the catalog charset), but not a mixture.
305309
"""
310+
needs_decode = None
306311

307312
for lineno, line in enumerate(fileobj):
308313
line = line.strip()
309-
if not isinstance(line, str):
310-
line = line.decode(self.catalog.charset)
314+
if needs_decode is None:
315+
# If we don't yet know whether we need to decode,
316+
# let's find out now.
317+
needs_decode = not isinstance(line, str)
311318
if not line:
312319
continue
320+
if needs_decode:
321+
line = line.decode(self.catalog.charset)
313322
if line[0] == '#':
314323
if line[:2] == '#~':
315324
self._process_message_line(lineno, line[2:].lstrip(), obsolete=True)

tests/messages/test_pofile.py

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1068,11 +1068,18 @@ def test_iterable_of_strings():
10681068
"""
10691069
Test we can parse from an iterable of strings.
10701070
"""
1071-
catalog = pofile.read_po(['msgid "foo"', b'msgstr "Voh"'], locale="en_US")
1071+
catalog = pofile.read_po(['msgid "foo"', 'msgstr "Voh"'], locale="en_US")
10721072
assert catalog.locale == Locale("en", "US")
10731073
assert catalog.get("foo").string == "Voh"
10741074

10751075

1076+
@pytest.mark.parametrize("order", [1, -1])
1077+
def test_iterable_of_mismatching_strings(order):
1078+
# Mixing and matching byteses and strs in the same read_po call is not allowed.
1079+
with pytest.raises(Exception): # noqa: B017 (will raise either TypeError or AttributeError)
1080+
pofile.read_po(['msgid "foo"', b'msgstr "Voh"'][::order])
1081+
1082+
10761083
def test_issue_1087():
10771084
buf = StringIO(r'''
10781085
msgid ""

0 commit comments

Comments
 (0)