Skip to content

Commit 3812c6b

Browse files
raise clear error when plain file references a binary resource
Referencing a binary file from a .plain file crashed with "cannot access local variable 'content_text' where it is not associated with a value" because open_from() swallowed UnicodeDecodeError but still returned the never-assigned variable. Now the decode error propagates and load_linked_resources() converts it into an UnsupportedResourceType naming the offending file.
1 parent 70150ef commit 3812c6b

4 files changed

Lines changed: 55 additions & 8 deletions

File tree

file_utils.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88

99
import plain_spec
1010
from plain2code_console import console
11+
from plain2code_exceptions import UnsupportedResourceType
1112
from plain2code_nodes import Plain2CodeIncludeTag, Plain2CodeLoaderMixin
1213
from plain_modules import CODEPLAIN_MEMORY_SUBFOLDER, CODEPLAIN_METADATA_FOLDER
1314

@@ -217,13 +218,7 @@ def open_from(dirs, file_name):
217218

218219
with open(full_file_name, "rb") as f:
219220
content = f.read()
220-
try:
221-
content_text = content.decode("utf-8")
222-
except UnicodeDecodeError:
223-
console.debug(
224-
f"WARNING! Error loading {file_name} ({file_name}). File is not a text file. Skipping it."
225-
)
226-
return content_text
221+
return content.decode("utf-8")
227222

228223
return None
229224

@@ -236,7 +231,13 @@ def load_linked_resources(template_dirs: list[str], resources_list):
236231
if file_name in linked_resources:
237232
continue
238233

239-
content = open_from(template_dirs, file_name)
234+
try:
235+
content = open_from(template_dirs, file_name)
236+
except UnicodeDecodeError:
237+
raise UnsupportedResourceType(
238+
f"Referenced resource '{file_name}' is a binary file. "
239+
f"Only text files (e.g. .md, .txt, .json, .yaml) can be referenced from a .plain file."
240+
)
240241

241242
if content is None:
242243
raise FileNotFoundError(f"""File not found:

plain2code.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
PlainSyntaxError,
3434
RenderCancelledError,
3535
RenderingCreditBalanceTooLow,
36+
UnsupportedResourceType,
3637
)
3738
from plain2code_logger import (
3839
LOGGER_NAME,
@@ -350,6 +351,7 @@ def main(): # noqa: C901
350351
RenderingCreditBalanceTooLow,
351352
NetworkConnectionError,
352353
ModuleDoesNotExistError,
354+
UnsupportedResourceType,
353355
),
354356
):
355357
exc_info = sys.exc_info()

plain2code_exceptions.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,10 @@ class MissingResource(Exception):
2121
pass
2222

2323

24+
class UnsupportedResourceType(Exception):
25+
pass
26+
27+
2428
class PlainSyntaxError(Exception):
2529
pass
2630

tests/test_file_utils.py

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
import os
2+
import tempfile
3+
4+
import pytest
5+
6+
from file_utils import load_linked_resources
7+
from plain2code_exceptions import UnsupportedResourceType
8+
9+
10+
@pytest.fixture
11+
def template_dir():
12+
with tempfile.TemporaryDirectory() as d:
13+
yield d
14+
15+
16+
def test_load_linked_resources_text_file(template_dir):
17+
file_path = os.path.join(template_dir, "notes.md")
18+
with open(file_path, "w") as f:
19+
f.write("# hello")
20+
21+
result = load_linked_resources([template_dir], [{"text": "Notes", "target": "notes.md"}])
22+
23+
assert result == {"notes.md": "# hello"}
24+
25+
26+
def test_load_linked_resources_binary_file_raises_unsupported_resource_type(template_dir):
27+
file_path = os.path.join(template_dir, "icon.png")
28+
with open(file_path, "wb") as f:
29+
f.write(b"\x89PNG\r\n\x1a\n\x00\x00\x00\rIHDR\xff\xfe\xfd")
30+
31+
with pytest.raises(UnsupportedResourceType) as exc_info:
32+
load_linked_resources([template_dir], [{"text": "Icon", "target": "icon.png"}])
33+
34+
assert "icon.png" in str(exc_info.value)
35+
assert "binary file" in str(exc_info.value)
36+
37+
38+
def test_load_linked_resources_missing_file_raises_file_not_found(template_dir):
39+
with pytest.raises(FileNotFoundError):
40+
load_linked_resources([template_dir], [{"text": "Missing", "target": "missing.md"}])

0 commit comments

Comments
 (0)