Fix graphie processing - #604
Conversation
|
""" WalkthroughThe changes refactor image URL handling in Perseus question processing, unify and simplify the logic for image replacement using regex, enhance file protocol handling in the disk resource handler, and update tests to use parameterized data instead of fixtures. A new Perseus bar graph labeling exercise JSON is also added for testing. Changes
Sequence Diagram(s)sequenceDiagram
participant PerseusQuestion
participant RegexSub
participant _replace_image
participant FileHandler
PerseusQuestion->>RegexSub: Apply IMAGE_URL_REGEX to raw_data
RegexSub->>_replace_image: For each image URL match
_replace_image->>FileHandler: Check if file exists or create file object with ka_language
FileHandler-->>_replace_image: Return file reference
_replace_image-->>RegexSub: Return content storage replacement string
RegexSub-->>PerseusQuestion: Return processed raw_data with replaced image URLs
Assessment against linked issues
📜 Recent review detailsConfiguration used: CodeRabbit UI 📒 Files selected for processing (1)
🧰 Additional context used🧠 Learnings (1)ricecooker/classes/files.py (4)⏰ Context from checks skipped due to timeout of 90000ms (5)
🔇 Additional comments (6)
✨ Finishing Touches
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. Note: Be mindful of the bot's finite context window. It's strongly recommended to break down tasks such as reading entire modules into smaller chunks. For a focused discussion, use review comments to chat about specific files and their changes, instead of using the PR comments. CodeRabbit Commands (Invoked using PR comments)
Other keywords and placeholders
CodeRabbit Configuration File (
|
There was a problem hiding this comment.
Actionable comments posted: 2
🧹 Nitpick comments (4)
ricecooker/utils/pipeline/transfer.py (1)
62-64:should_handlemay mis-report on case-insensitive filesystems
os.path.existsis case-sensitive on POSIX but not on Windows. A Windows drive typed with the wrong case (e.g.file:///c:/…) will be normalised toc:\…andexistswill still succeed, hiding mismatches that could surface later (e.g. when hashing). Consider usingos.path.lexistsplus an explicit case check oros.path.realpathto canonicalise paths so the cache key and subsequent hashing are stable across platforms.tests/pipeline/test_transfer.py (1)
252-266: Guard against spaces & special characters in temporaryfile://URIsWhen building the
file://URI the test concatenates the raw path, but paths undertests/testcontentcan legally contain spaces or#,%, etc. Those characters must be percent-encoded for a standards-compliant URI; while the current code happens to work on most platforms, it will fail once such a filename is added.- if platform == "win32": - file_path_with_protocol = "file:///" + file_path.replace("\\", "/") + from urllib.parse import quote + if platform == "win32": + file_path_with_protocol = "file:///" + quote(file_path.replace("\\", "/")) else: - file_path_with_protocol = "file://" + file_path + file_path_with_protocol = "file://" + quote(file_path)Encoding here improves cross-platform fidelity and exercises the new decode logic in
_normalize_path.ricecooker/classes/questions.py (1)
18-19: Regex can over-match and miss single-quoted or escaped URLs
IMAGE_URL_REGEXstops at")but Perseus JSON occasionally contains single-quoted attributes ('imageUrl':'http://...') or escaped\/slashes. A more future-proof pattern would be:IMAGE_URL_REGEX = ( r"(?P<protocol>web\+graphie|https?|file|data):" r"(?P<rawpath>(?:\\.|[^'\"\\\)\s])+)" )This excludes whitespace, closing quotes and escaped characters while supporting both
'and"delimiters.tests/test_exercises.py (1)
209-215: Fix error message formatting in the assertionThe error message concatenation will attempt to add tuples to strings, which will produce an unexpected error format. Use f-strings for more readable error messages.
assert m.groups() == expected_matches, ( - "found " + m.groups() + " expected " + expected_matches + f"found {m.groups()} expected {expected_matches}" )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
ricecooker/classes/questions.py(6 hunks)ricecooker/utils/pipeline/transfer.py(3 hunks)tests/pipeline/test_transfer.py(2 hunks)tests/test_exercises.py(2 hunks)tests/testcontent/exercises/perseus_question_new_bar_graphs.json(1 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (2)
ricecooker/utils/pipeline/transfer.py (1)
ricecooker/utils/pipeline/file_handler.py (3)
should_handle(34-36)should_handle(275-280)should_handle(294-295)
ricecooker/classes/questions.py (3)
ricecooker/utils/encodings.py (1)
get_base64_encoding(8-14)tests/conftest.py (1)
exercise_image_file(852-858)ricecooker/classes/files.py (11)
_ExerciseGraphieFile(395-458)_ExerciseBase64ImageFile(381-385)_ExerciseImageFile(388-392)process_file(167-168)process_file(198-219)process_file(417-429)process_file(468-490)process_file(548-551)get_replacement_str(384-385)get_replacement_str(391-392)get_replacement_str(411-415)
⏰ Context from checks skipped due to timeout of 90000ms (5)
- GitHub Check: Python unit tests (ubuntu-latest, 3.9)
- GitHub Check: Python unit tests (windows-latest, 3.12)
- GitHub Check: Python unit tests (windows-latest, 3.11)
- GitHub Check: Python unit tests (windows-latest, 3.10)
- GitHub Check: Python unit tests (windows-latest, 3.9)
🔇 Additional comments (9)
tests/test_exercises.py (3)
243-276: LGTM! Good use of parameterized testingThe conversion from fixtures to parameterized tests makes the test clearer and failures easier to trace. The test correctly verifies that the
set_imagemethod performs the expected replacements and saves images properly.
281-334: Well-structured test data setupGood approach using module-level variables instead of fixtures for test data. The PR objective to fix the issue with new widget types (specifically bar graphs) is addressed with the inclusion of the new test case.
336-361: LGTM! Dynamic assertion for image hashesThe test now dynamically checks the number of expected image hashes rather than using a fixed count, making it more maintainable as new test cases are added. This approach aligns well with the PR objective to improve handling of Perseus JSON.
tests/testcontent/exercises/perseus_question_new_bar_graphs.json (6)
1-8: Answer area configuration is appropriate.The
answerAreaobject correctly disables all calculators and statistical tables for this labelling exercise. No additional tables appear necessary.
9-20: Hint 1 image mapping looks correct.The first hint embeds a
web+graphie://URL and provides matchingheight/widthmetadata underimages. This aligns with the unified regex-based image replacement approach.
33-43: Hint 4 image mapping is sound.The final hint also embeds a
web+graphie://URL with correct dimension metadata. This will be processed by the same replacement logic.
45-48: Item data version is set appropriately.Using
0.1here is consistent with other Perseus fixtures.
49-52: Question content and placeholder look correct.The main question text and the
[[☃ label-image 1]]widget placeholder align with the intended bar-labeling exercise.
53-113: Widget configuration is complete.The
label-imagewidget definesimageUrl, dimensions, and four markers with the expected answer choices. All fields are present and valid for image replacement and grading.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
ricecooker/classes/questions.py (1)
292-294: Fixed handling of newlines in web+graphie URLsThis change properly addresses the previous issue where
stripped_pathwas discarded and newline trimming never took effect. By first cleaning the path and then using it in the replacement, web+graphie URLs with stray newlines will now be handled correctly.
🧹 Nitpick comments (1)
ricecooker/classes/questions.py (1)
289-290: Consider using stripped_path consistentlyWhile you're properly handling
stripped_pathfor web+graphie URLs, for other protocols you're still using the originalpathwhich might contain whitespace. Consider using the cleaned path consistently for all protocols.- full_path = f"{protocol}:{path}" + full_path = f"{protocol}:{stripped_path}"
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
ricecooker/classes/questions.py(6 hunks)ricecooker/utils/pipeline/transfer.py(3 hunks)tests/pipeline/test_transfer.py(2 hunks)tests/test_exercises.py(2 hunks)tests/testcontent/exercises/perseus_question_new_bar_graphs.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (3)
- ricecooker/utils/pipeline/transfer.py
- tests/testcontent/exercises/perseus_question_new_bar_graphs.json
- tests/test_exercises.py
⏰ Context from checks skipped due to timeout of 90000ms (5)
- GitHub Check: Python unit tests (ubuntu-latest, 3.9)
- GitHub Check: Python unit tests (windows-latest, 3.12)
- GitHub Check: Python unit tests (windows-latest, 3.11)
- GitHub Check: Python unit tests (windows-latest, 3.10)
- GitHub Check: Python unit tests (windows-latest, 3.9)
🔇 Additional comments (11)
tests/pipeline/test_transfer.py (6)
4-6: Well-organized import additions for platform-specific testing.These imports provide the necessary tools for platform detection and mocking file operations, which are essential for testing the new file:// URL handling capabilities without depending on actual file system resources.
254-279: Well-implemented cross-platform file:// protocol test.This test properly handles platform differences between Windows and Unix-like systems when constructing file:// URLs. It creates a valid path to an existing test file and verifies that the handler correctly processes it, preserving the file extension.
281-312: Comprehensive Windows UNC path handling test with proper mocking.The test correctly:
- Uses pytest.mark.skipif to avoid running on non-Windows platforms
- Mocks file operations to avoid requiring actual network shares
- Verifies URL normalization from file://server/share/path to proper Windows UNC format (\server\share\path)
- Confirms metadata extraction works correctly
This is essential for supporting file references in non-Khan Academy Perseus JSON as mentioned in the PR objectives.
314-337: Good coverage for IP-based UNC paths.The test extends UNC path support to include IP-based paths (like \192.168.1.100\share), which are common in enterprise environments. The mocking approach avoids dependency on actual network resources while still providing thorough test coverage.
339-371: Thorough testing of Windows file:// URL edge cases.This test covers important edge cases:
- Multiple leading slashes
- URL-encoded spaces and special characters
- URLs with query parameters or fragments
These cases ensure robust handling of various file:// URL formats that might be encountered in real-world scenarios.
373-386: Important negative test case for non-file protocols.This test verifies that:
- Non-file URLs are left unchanged by the normalization process
- The handler correctly refuses to process URLs with other protocols
This prevents the handler from accidentally processing URLs it shouldn't handle, maintaining separation of responsibilities between different resource handlers.
ricecooker/classes/questions.py (5)
18-18: Improved protocol support with comprehensive regex patternThe new
IMAGE_URL_REGEXpattern enhances URL handling by supporting multiple protocols (web+graphie, http, https, file, data), which aligns with the PR objective to add file:// protocol support and treat URLs consistently.
143-143: Simplified API by removing optional parameterRemoving the conditional parsing of HTML streamlines the code path, ensuring consistent handling of all text content.
325-325: Simplified Perseus question processing with regex substitutionThe previous approach that recursively traversed the JSON structure has been replaced with a more direct regex-based substitution that treats the JSON as a string. This is a cleaner approach that aligns with the PR objective of removing opinionated processing from raw Perseus JSON parsing.
282-318:Details
❓ Verification inconclusive
New _replace_image method consolidates image processing logic
This new method centralizes the image replacement logic that was previously scattered across multiple methods, improving maintainability and ensuring consistent handling of image URLs regardless of their source.
However, there's a potential issue: when using protocols other than web+graphie, you're using the original
pathinfull_path(line 289) but not applying the same newline cleanup that you do for web+graphie URLs.- full_path = f"{protocol}:{path}" + # Create clean path for all protocols, not just web+graphie + cleaned_path = stripped_path.lstrip("\\n").rstrip("\\n") + full_path = f"{protocol}:{cleaned_path}"Let's verify if paths with other protocols might contain newlines:
🏁 Script executed:
#!/bin/bash # Check if any path with http/https/file protocol contains encoded newlines rg -i "(?:http|https|file):[^\"')]*\\\\n[^\"')]*" --no-filenameLength of output: 3496
Potential newline cleanup gap for non-graphie protocols
Our regex search (
rg -i "(?:http|https|file):[^\"')]*\\\\n") surfaced\\nescapes in HTTP and FILE URIs (e.g. warnings and Facebook/YouTube links). While onlyweb+graphiecurrently strips stray\\n, other protocols may similarly pick up line-break artifacts. To harden this method, consider normalizingstripped_pathfor all protocols:- full_path = f"{protocol}:{path}" + # Trim any stray encoded newline characters for any protocol + cleaned_path = stripped_path.lstrip("\\n").rstrip("\\n") + full_path = f"{protocol}:{cleaned_path}"Please verify whether image URLs from HTTP/HTTPS/FILE ever contain these
\\nsequences in practice, and apply this change if so.
302-303:Details
❌ Incorrect review comment
Verify base64 encoding check is using the correct value
The
get_base64_encodingfunction is being called withfull_path, which includes the protocol prefix. Verify that this function correctly handles input with protocol prefixes, or consider passing only the path portion.
🏁 Script executed:
#!/bin/bash # Check how get_base64_encoding is implemented and what it expects rg -A 10 "def get_base64_encoding" --include "*.py"Length of output: 495
Let’s locate the
get_base64_encodingimplementation to confirm its expected input. Please run:This will help verify whether it handles protocol prefixes or expects a plain file path.
🏁 Script executed:
#!/bin/bash # Search for get_base64_encoding implementation across the repository rg "def get_base64_encoding" -A 10Length of output: 709
To confirm how
BASE64_REGEXis defined and whether it will match a data-URI string (including thedata:image/...;base64,prefix), please run:
🏁 Script executed:
#!/bin/bash # Display BASE64_REGEX in encodings.py rg "BASE64_REGEX" -n -A2 -B2 ricecooker/utils/encodings.pyLength of output: 367
No changes needed for base64 check
The
get_base64_encodingfunction uses a regex that explicitly looks for the full data-URI prefix (data:image/...;base64,). Passingfull_pathdirectly is correct—it will only return a match whenfull_pathis already a data URI, and safely returnNoneotherwise. There’s no need to strip or modify the input beforehand.Likely an incorrect or invalid review comment.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
ricecooker/classes/questions.py (1)
282-318: Add documentation for protocol handlingThe new method supports multiple protocols but lacks documentation about how each is handled, especially the newly added
file://protocol that's mentioned in the PR objectives.Consider adding documentation to the method explaining the purpose and how each protocol is handled:
def _replace_image(self, match): + """ + Replace image URLs in Perseus JSON with content storage URLs. + + Handles multiple protocols: + - web+graphie: Removes newlines and converts to https + - http/https: Uses the URL directly + - file: Handles local file system references + - data: Processes data URLs (e.g., base64 encoded images) + + Args: + match: A regex match object with named groups 'protocol' and 'rawpath' + + Returns: + The replacement URL string formatted for content storage + """ protocol = match.group("protocol") path = match.group("rawpath")
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (5)
ricecooker/classes/questions.py(6 hunks)ricecooker/utils/pipeline/transfer.py(3 hunks)tests/pipeline/test_transfer.py(2 hunks)tests/test_exercises.py(2 hunks)tests/testcontent/exercises/perseus_question_new_bar_graphs.json(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (4)
- tests/pipeline/test_transfer.py
- tests/testcontent/exercises/perseus_question_new_bar_graphs.json
- ricecooker/utils/pipeline/transfer.py
- tests/test_exercises.py
🔇 Additional comments (4)
ricecooker/classes/questions.py (4)
18-18: Support for multiple protocols adds flexibilityThe new image URL regex pattern now supports multiple protocols including
web+graphie,http/https,file, anddata, which aligns with the PR objectives to handle local file references.
290-295: Fixed the newline trimming issue from previous reviewThe code now properly handles newline characters in web+graphie URLs and correctly assigns the cleaned value to
full_path. This fixes the issue raised in the previous review where the trimmed value was discarded.
325-325: Simplified regex-based image URL replacementUsing a single regex replacement instead of recursive JSON traversal makes the code much cleaner and easier to maintain. This aligns with the PR objective of treating Perseus JSON as plain text for image replacement.
134-143:Details
✅ Verification successful
Parameter removal changes API behavior
The
parse_htmlparameter has been removed fromset_images(), making HTML parsing mandatory. Ensure this change doesn't break existing code that relied on the optional behavior.
🏁 Script executed:
#!/bin/bash # Search for any calls to set_images that explicitly set parse_html=False rg "set_images\([^,]+,\s*parse_html\s*=\s*False" --type pyLength of output: 60
Here are additional checks to locate any positional or named uses of the removed parameter:
🏁 Script executed:
#!/bin/bash # List all calls to set_images rg "set_images\(" -n --type py # Find any calls passing False as a second (positional) argument rg "set_images\([^)]*,\s*False" -n --type py # Find any calls using the named parameter parse_html rg "set_images\(.*parse_html" -n --type pyLength of output: 505
Internal calls to set_images don’t use the removed
parse_htmlparameter
We searched all calls toset_images()and found only three instances inricecooker/classes/questions.py, none passingparse_html. Removing this optional flag does not affect existing internal usage.• ricecooker/classes/questions.py:101
• ricecooker/classes/questions.py:108
• ricecooker/classes/questions.py:125If
set_images()is exposed as a public API, please update its documentation to note that HTML parsing is now mandatory.
Update Perseus question processing to treat the raw_data as text. Update exercise tests to use pytest parameterize, and to remove graphie testing from anything but Perseus question. Remove unused perseus question tests.
…andle translations.
There was a problem hiding this comment.
Actionable comments posted: 4
🧹 Nitpick comments (5)
ricecooker/classes/files.py (2)
459-466: Useurllib.parse.urljoinfor URL compositionManual
rpartition("/")+ string concatenation risks double slashes or missing separators.
urljoin(base_path + "/", f"{self.ka_language}/{file_hash}")is clearer and handles edge-cases (query/fragment) out-of-the-box.
479-481: Consider caching the English variant after fallbackWhen localisation is missing we skip caching (
should_cache = False).
This forces a network round-trip on every subsequent build, even though we successfully downloaded the English JSON.
Writing to cache with a key that encodeslang=enstill avoids polluting the locale-specific cache while saving bandwidth.ricecooker/classes/questions.py (2)
134-154:set_imagesruns HTML→Markdown conversion every call – heavy for large strings
parse_html()invokes BeautifulSoup+html5lib (slow) even when no<img>tags exist.
A quick guard dramatically reduces overhead:-if "<img" in text.lower(): - processed_string = self.parse_html(text) -else: - processed_string = text +processed_string = self.parse_html(text) if "<img" in text.lower() else text
284-315:_replace_imagetreatsfile:URLs as remote paths – loses local-file optimisation
full_pathremainsfile://..., which is later handed to_ExerciseImageFile→DownloadFile→FilePipeline.
Although_transfernow strips the scheme, we still pay an HTTP-download penalty for local files that already exist.Minor change:
elif protocol == "file": local_path = stripped_path.lstrip("/") # strip leading `//` exercise_image_file = _ExerciseImageFile(local_path)This skips the transfer layer altogether and avoids double disk I/O.
tests/test_exercises.py (1)
210-215: Assertion message will raiseTypeErroron failure
"found " + m.groups() + " expected " + expected_matchesconcatenatesstrandtuple.
If the assertion ever fails, the message generation itself explodes, masking the real cause.- assert m.groups() == expected_matches, ( - "found " + m.groups() + " expected " + expected_matches - ) + assert m.groups() == expected_matches, ( + f"found {m.groups()} expected {expected_matches}" + )
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (7)
examples/oldexamples/sample_program.py(1 hunks)ricecooker/classes/files.py(4 hunks)ricecooker/classes/questions.py(7 hunks)ricecooker/utils/jsontrees.py(1 hunks)tests/conftest.py(1 hunks)tests/test_exercises.py(2 hunks)tests/test_files.py(5 hunks)
🧰 Additional context used
🧬 Code Graph Analysis (3)
ricecooker/classes/files.py (3)
tests/test_files.py (2)
get(52-53)iter_content(1114-1129)tests/conftest.py (1)
iter_content(885-910)ricecooker/utils/utils.py (1)
copy_file_to_storage(75-90)
ricecooker/classes/questions.py (3)
ricecooker/utils/encodings.py (1)
get_base64_encoding(8-14)tests/conftest.py (1)
exercise_image_file(852-858)ricecooker/classes/files.py (11)
_ExerciseGraphieFile(396-481)_ExerciseBase64ImageFile(382-386)_ExerciseImageFile(389-393)process_file(168-169)process_file(199-220)process_file(419-431)process_file(491-513)process_file(571-574)get_replacement_str(385-386)get_replacement_str(392-393)get_replacement_str(413-417)
tests/test_exercises.py (3)
tests/test_videos.py (1)
_clear_ricecookerfilecache(294-308)ricecooker/classes/questions.py (5)
BaseQuestion(22-229)set_image(176-203)PerseusQuestion(232-326)process_question(95-132)process_question(317-326)ricecooker/classes/files.py (1)
get_filename(103-104)
⏰ Context from checks skipped due to timeout of 90000ms (5)
- GitHub Check: Python unit tests (ubuntu-latest, 3.9)
- GitHub Check: Python unit tests (windows-latest, 3.12)
- GitHub Check: Python unit tests (windows-latest, 3.11)
- GitHub Check: Python unit tests (windows-latest, 3.10)
- GitHub Check: Python unit tests (windows-latest, 3.9)
🔇 Additional comments (4)
ricecooker/utils/jsontrees.py (1)
436-436: Adding language support for Perseus QuestionsThis change adds language context to Perseus questions, enabling proper handling of localized graphie files. The implementation correctly retrieves the language parameter from the question dictionary.
examples/oldexamples/sample_program.py (1)
572-572: Consistent update to pass language parameter to Perseus questionsThis change maintains consistency with the PerseusQuestion constructor update, ensuring proper language context is provided for graphie processing in sample code as well.
tests/conftest.py (1)
927-927: Updated test fixture to match new _ExerciseGraphieFile signatureThe fixture has been properly updated to provide the required language parameter, ensuring tests continue to work with the refactored graphie processing implementation.
tests/test_files.py (1)
1146-1146: Consistently updated all _ExerciseGraphieFile instantiations with language parameterAll test cases using _ExerciseGraphieFile have been properly updated to include the new required language parameter. This ensures test coverage remains intact after the implementation changes to support language-specific graphie processing.
Also applies to: 1161-1161, 1182-1182, 1192-1192, 1200-1200, 1210-1210, 1215-1215
There was a problem hiding this comment.
Added a couple of questions. (Another perk of the coderabbit review - helps me understand some of the code and decision making. It's nice to come in after that as a reviewer). They're not in and of themselves blocking, but i want to reread this again tomorrow and see if I have any other thoughts or questions, because it's taking me kind of a while to digest it, and then I'll go ahead and approve.
| processed_string = self.parse_html(text) | ||
| else: | ||
| processed_string = text | ||
| processed_string = self.parse_html(text) |
There was a problem hiding this comment.
I understand the change here in a readability sense, but I don't understand the reason for the change
There was a problem hiding this comment.
This method used to be used for both !PerseusQuestions and PerseusQuestions. PerseusQuestions should not contain HTML, so we did not parse them as such.
With the changes I made, this method is now only used by !PerseusQuestions so the conditionality was no longer required.
| r = config.DOWNLOAD_SESSION.get(json_path_base + "-data.json", stream=True) | ||
| r.raise_for_status() | ||
| except HTTPError: | ||
| if self.ka_language == "en": |
There was a problem hiding this comment.
So, based on the conversation with coderabbit, the reasoning for this is to fallback to english to confirm whether or not the resource exists at all, vs. if it's a temporary connection error that should be retried?
There was a problem hiding this comment.
The HTTPError indicates there is something wrong with the resource, rather than it just be a temporary connection or timeout issue, so either the resource doesn't exist, is forbidden or whatnot. In that case, we try to get the English version instead, in case it just hasn't been translated yet and that's why we can't access it. Under the rationale that a poorly translated graphic is better than no graphic.
Summary
References
Needed to fix learningequality/kolibri-library#20
Reviewer guidance
Have I inadvertently removed some test coverage that might mean that question images are not being properly handled now?
Summary by CodeRabbit
New Features
Refactor
_ExerciseGraphieFilehandling to support language-specific content retrieval and caching.Bug Fixes
Tests
file://protocol._ExerciseGraphieFileinstantiation.