Repository navigation
Accept plain matplotlib Axes and Figure values as JSON - #32
Draft
eliotwrobson wants to merge 2 commits into
Draft
eliotwrobson wants to merge 2 commits into
eliotwrobson wants to merge 2 commits into
Conversation
Student code had to build plots with MatplotlibSerializer. It can now provide a plain Axes or Figure, which the grader receives as a Figure, sent as validated JSON data with no pickling. An Axes brings its whole figure, so subplots aren't lost. Lines, scatter points, bars, histograms, scales, labels, titles, limits and legend labels are carried. Anything else, such as errorbar plots, images or text, is rejected with a clear error rather than losing data. The sandbox now defaults matplotlib to the Agg backend, because student code runs on a worker thread with no display. Importing matplotlib in the new unit tests means the sandbox has to build its own font cache in pytester's temporary home directory, so test_matplotlib_plots needs a longer timeout. The same change is in #27. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
matplotlib-stubs types get_yscale as an ellipsis and leaves out Figure.get_suptitle, so annotate those values as Any. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Student code can now provide a plain matplotlib
AxesorFigure, such asplot = plt.gca()afterplt.semilogy(x, fx). The test receives aFigure, the same type as theMatplotlibSerializerpath, which still works.The data is plain JSON with no pickling. It comes from student code, so rebuilding validates every field and treats it as untrusted. Malformed data raises a
ValueError.What's carried and rejected
Carried: lines, including
semilogy,loglogandstep; scatter points with sizes and colors; bars and histograms; scales, labels, titles, limits, positions and legend labels; figure size and overall title.Rejected with a clear error:
errorbar,stem,fill_between,pie,imshow,contour,vlinesandhlines,axhlineandaxvline,axline,axvspan, step histograms, text and annotations, colormapped scatter plots, anything not in data coordinates, polar axes, inset axes, subfigures, and items added directly to a figure. The error reaches the student through the "can't be sent back to the grader" message.Decisions:
Axesvalue returns its whole figure, so subplots are never lost and tests always get aFigure.symlogsettings.Sandbox change: it now defaults matplotlib to the
Aggbackend, set inside the sandbox process. Student code runs on a worker thread, and plainpltfailed there with macOS's GUI backend.test_matplotlib_plotsgets a longersandbox_timeout: importing matplotlib in the new unit tests means the sandbox has to build its own font cache underpytester's temporary home, which took about 7.5s here. The same line is in #27.Testing
uv run pytest tests -p no:prairielearn-graderon Python 3.14: all 147 passed on two runs. New unit tests cover round trips, rejections and malformed data, and a new scenario coversqueryandquery_function.uv run mypy --config-file pyproject.toml src tests, the command CI runs, is clean.ruff checkhas three newPLC0415lazy-import findings injson_utils.py, the same pattern used there for other optional imports.docker/files, with this branch's wheel installed over the PyPI package. I confirmed the installedmatplotlib_json.py,json_utils.pyand_student_code_runner.pymatch the branch. It has matplotlib 3.11.2; local tests used 3.10.8.plotstest_student.py, unchanged, with student code running asag:plt.semilogy+plt.gca()plt.semilogy+plt.gcf()plt.plot(wrong scale)plot does not have the correct scale for its axesplt.semilogyof wrong dataplot is inaccurateplt.errorbarVariable 'plot' has a value of type Axes, which can't be sent back to the grader: errorbar plots aren't supportedplot should have exactly one set of axesplot = 5plot is not a MatplotlibSerializer objectMatplotlibSerializerinitial codeag: the new scenario scored 5/6, where the one failure is deliberate and checks the message, includingPlotTesteron a rebuilt figure.test_matplotlib_plotsscored 12/12.Startup as
ag:HOMEstays/root, whichagcan't write to, so each sandbox creates a temporary matplotlib folder in/tmpand prints a warning to its stderr. Importingmatplotlib.pyplottook about 0.75s. With the question'ssandbox_timeoutremoved, the default 1s passed 5 of 5 tries on this machine, but the margin is thin, so the docs recommend a longer timeout for plot questions.Not tested: categorical, date, NaN and subplot values are covered by unit tests only, not graded in the image. A workspace question with a restrictive import whitelist could block the lazy import of
matplotlib_json, based on reading the code.🤖 Generated with Claude Code