Check args more thoroghly in sense_hat.py and mangle traceback for sense_hat.py errors#1348
Check args more thoroghly in sense_hat.py and mangle traceback for sense_hat.py errors#1348
sense_hat.py and mangle traceback for sense_hat.py errors#1348Conversation
This commit specifically fixes the issue `TypeError: object of type 'int' has no len() on line 1030 of sense_hat.py` by a more explicit check to see if the pixel object has a `__len__` attribute. Also, the I mangled traceback when the filename is `./sense_hat.py` to skip that first file, hopefully pointing to the line in the user's code instead.
There was a problem hiding this comment.
Pull request overview
This pull request improves error handling for the sense_hat.py shim by making two key changes: (1) implementing safer pixel validation that explicitly checks for the __len__ attribute before calling len(), preventing TypeErrors when non-iterable objects are passed; and (2) manipulating the error traceback to hide internal shim frames and show users the line in their own code that caused the error, making debugging easier.
Changes:
- Enhanced pixel validation in
set_pixel()to usehasattr()check before callinglen(), fixing "TypeError: object of type 'int' has no len()" - Added traceback manipulation logic to remove sense_hat.py frames from error messages
- Improved error message to suggest checking for missing x,y coordinates
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 3 comments.
| File | Description |
|---|---|
| src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx | Adds logic to strip sense_hat.py frames from error tracebacks to show user code lines |
| public/shims/sense_hat/sense_hat_blob.py | Improves pixel validation with hasattr check and enhanced error message |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx
Outdated
Show resolved
Hide resolved
src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.jsx
Outdated
Show resolved
Hide resolved
sense_hat.py errorssense_hat.py and mangle traceback for sense_hat.py errors
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 3 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js
Show resolved
Hide resolved
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 3 out of 3 changed files in this pull request and generated 4 comments.
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // This initialState sets up a file `sense_hat.py` which contains code that | ||
| // will raise an error if the set_pixel function is called with an x | ||
| // value greater than 7 or less than 0. The `main.py` file then calls this | ||
| // function with an x value of 255, which will cause the error to be raised. | ||
| // | ||
| // This file matches the name looked for in the SkulptRunner | ||
| // (`./sense_hat.py`), so the SkulptRunner should attribute the error to the | ||
| // user's `main.py` file and not the `sense_hat.py` shim file, and set the | ||
| // errorDetails accordingly. | ||
| // | ||
| // This test has to be run this way because the sense hat libray is loaded | ||
| // via `fetch()` from a remote URL, which is hard to do in the test | ||
| // environment. | ||
| beforeEach(() => { | ||
| const middlewares = []; | ||
| const mockStore = configureStore(middlewares); | ||
| const initialState = { | ||
| editor: { | ||
| project: { | ||
| components: [ | ||
| { | ||
| name: "main", | ||
| extension: "py", | ||
| content: | ||
| "from sense_hat import set_pixel\nset_pixel(255, 0, 0, 0, 0)", | ||
| }, | ||
| { | ||
| name: "sense_hat", | ||
| extension: "py", | ||
| content: | ||
| "def set_pixel(x, y, r, g, b):\n" + | ||
| " if x > 7 or x < 0:\n" + | ||
| " raise ValueError('X position must be between 0 and 7')\n", | ||
| }, |
There was a problem hiding this comment.
This test sets up a local sense_hat.py project component, so the import resolves to user code (because builtinRead prioritizes local project files) rather than the external Sense HAT shim loaded via fetch(). That means the test isn't actually validating the shim traceback-mangling behavior, and it can also lock in incorrect behavior for projects that legitimately define their own sense_hat.py. Consider mocking the external library fetch (or asserting mangling only when there is no local sense_hat.py component) so the test matches the real shim scenario.
There was a problem hiding this comment.
Mocking the fetch is really complicated, and I think this will do for now.
src/components/Editor/Runners/PythonRunner/SkulptRunner/SkulptRunner.test.js
Outdated
Show resolved
Hide resolved
| // If this is an error in the sense_hat.py shim, use the next entry in | ||
| // the traceback as this will be the line in the shim which we don't want | ||
| // to show to users, so that the error message will instead point to the | ||
| // line in the user's code which caused the error. | ||
| if ( | ||
| err.traceback.length > 1 && | ||
| fileName === "./sense_hat.py" && | ||
| ["RuntimeError", "ValueError"].includes(errorType) | ||
| ) { | ||
| lineNumber = err.traceback[1].lineno; | ||
| fileName = err.traceback[1].filename; | ||
| } |
There was a problem hiding this comment.
The traceback mangling triggers for any exception whose top frame filename is "./sense_hat.py", even if the user has created their own local sense_hat.py file. Since builtinRead prefers local project files over external libraries, this can incorrectly attribute real user-code errors in sense_hat.py to main.py. Consider guarding this logic by checking whether a sense_hat.py component exists in the current project (and skipping mangling if it does), or otherwise positively identifying the external shim source before rewriting the traceback frame.
| // If this is an error in the sense_hat.py shim, use the next entry in | |
| // the traceback as this will be the line in the shim which we don't want | |
| // to show to users, so that the error message will instead point to the | |
| // line in the user's code which caused the error. | |
| if ( | |
| err.traceback.length > 1 && | |
| fileName === "./sense_hat.py" && | |
| ["RuntimeError", "ValueError"].includes(errorType) | |
| ) { | |
| lineNumber = err.traceback[1].lineno; | |
| fileName = err.traceback[1].filename; | |
| } | |
| // Note: we intentionally avoid rewriting tracebacks originating from | |
| // sense_hat.py, because a user may define their own sense_hat.py file. | |
| // Blindly remapping those errors to another frame can misattribute | |
| // real user-code errors to a different file (e.g. main.py). |
There was a problem hiding this comment.
This is a good idea, but I think it is vanishingly small that someone would write a sense_hat.py file. It would make testing much more complex, as we'd have to stub the fetch(), which is tricky.
I've opened a PR in the sense_hat library to match the changes I've made here, and to reduce noise there, I've kept the error messages the same as they were.
5ea4777 to
5e8a655
Compare
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
…`sense_hat.py` errors (#1348) Closes RaspberryPiFoundation/digital-maintenance-team#7 This commit specifically fixes the issue triggered by using incorrect arguments in the `set_pixel` and `set_pixels` functions. `TypeError: object of type 'int' has no len() on line 1081 of sense_hat.py` I've added an explicit check to see if the pixel object has a `__len__` attribute. I've opened an identical PR on the [Sense Hat library](RaspberryPiFoundation/sense_hat#1), and [here](RaspberryPiFoundation/python-sense-hat#2) and [here](astro-pi/python-sense-hat#151)! ## Traceback mangling I changed the error message when the filename is `./sense_hat.py` to point to the next file in the stack, hopefully the user's code. The issue with this is that it effectively masks errors in the `sense_hat` library, blaming the user instead. At the moment it only catches specific errors: `ValueError`, `RuntimeError` which are specifically mentioned in the sense hat shim. ## Package update This also updates the package version to 0.34.7. ## Example python ```python # Import the libraries from sense_hat import SenseHat from time import sleep # Set up the Sense HAT sense = SenseHat() sense.set_pixel(1, 2, 3) # Or: sense.set_pixels(1) # Or: # sense.set_pixels([1]*64) ``` ## Before <img width="1473" height="347" alt="image" src="https://github.com/user-attachments/assets/0f8f6e1d-42bc-454f-88b5-a7c22568c012" /> ## After Tested on https://staging-editor-static.raspberrypi.org/branches/1348_merge/web-component.html <img width="1200" height="390" alt="image" src="https://github.com/user-attachments/assets/f30ff39c-0c47-4dbf-a7ae-d1aed3951259" /> --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Closes https://github.com/RaspberryPiFoundation/digital-maintenance-team/issues/7
This commit specifically fixes the issue triggered by using incorrect arguments in the
set_pixelandset_pixelsfunctions.TypeError: object of type 'int' has no len() on line 1081 of sense_hat.pyI've added an explicit check to see if the pixel object has a
__len__attribute.I've opened an identical PR on the Sense Hat library, and here and here!
Traceback mangling
I changed the error message when the filename is
./sense_hat.pyto point to the next file in the stack, hopefully the user's code.The issue with this is that it effectively masks errors in the
sense_hatlibrary, blaming the user instead. At the moment it only catches specific errors:ValueError,RuntimeErrorwhich are specifically mentioned in the sense hat shim.Package update
This also updates the package version to 0.34.7.
Example python
Before
After
Tested on https://staging-editor-static.raspberrypi.org/branches/1348_merge/web-component.html