Local Gradescope autograder runner
Project description
gslocal
Run your Gradescope autograder locally against any submission, without uploading anything to Gradescope.
gslocal mirrors Gradescope's exact container workflow: it builds your autograder zip, spins up the same Docker base image Gradescope uses, mounts the submission, runs the grader, and prints a formatted score summary. Build and image caching mean repeat runs are fast.
Requirements
- Python 3.9+
- Docker Desktop (running)
- Git
Installation
pip install gslocal
Quick Start
In your autograder project directory, run:
gslocal init
This walks you through creating a gslocal.toml config file. Then test a submission:
gslocal run path/to/submission.zip
How It Works
gslocal expects your autograder project to produce a Gradescope-compatible zip file via a build command. It is agnostic about your build tool - Maven, Make, a shell script, anything that outputs a zip works.
On each run, gslocal:
- Prepares the submission (zip, directory, or GitHub clone)
- Runs your build command if source files changed since the last build
- Generates a Dockerfile and builds a Docker image if the zip is fresh
- Runs the container with the submission mounted, waits for results
- Prints a formatted score summary
Build caching: gslocal hashes the files listed in your watch config. If nothing changed and the output zip exists, the build is skipped entirely. The Docker image is similarly only rebuilt when the zip changes.
Configuration
gslocal is configured via a gslocal.toml file in your autograder project root. Run gslocal init to generate one interactively.
[build]
cmd = "mvn clean package" # command to produce the autograder zip
zip = "target/*.zip" # glob pattern locating the output zip
watch = ["src/**/*", "pom.xml"] # glob patterns to watch for rebuild triggering
[docker]
setup = "src/main/resources/setup.sh" # path to setup.sh inside the project (required)
# metadata = "src/main/resources/mock_submission_metadata.json" # optional
[run]
timeout = 60 # autograder timeout in seconds
verbose = false # show full build/docker output instead of spinners
Field reference:
| Field | Required | Description |
|---|---|---|
build.cmd |
yes | Shell command to build the autograder zip |
build.zip |
yes | Glob pattern to locate the output zip |
build.watch |
yes | Glob patterns whose content is hashed to detect source changes |
docker.setup |
yes | Path to setup.sh relative to project root |
docker.metadata |
no | Path to mock submission metadata JSON; a bundled default is used if omitted |
run.timeout |
no | Container timeout in seconds (default: 60) |
run.verbose |
no | Show full build and Docker output (default: false) |
gslocal locates gslocal.toml by walking up the directory tree from your current working directory, so you can run it from any subdirectory of your project.
Submission Types
gslocal accepts submissions in three formats:
# Zip file
gslocal run test-zips/solution.zip
# Local directory
gslocal run /path/to/student/project/
# GitHub URL (SSH or HTTPS)
gslocal run git@github.com:student/repo.git
gslocal run https://github.com/student/repo
GitHub submissions are cloned with --depth 1.
Commands
gslocal run <submission>
Run the autograder against a submission.
| Flag | Description |
|---|---|
-r, --rebuild |
Force rebuild of build command and Docker image |
-n, --no-build |
Skip build, use existing zip |
-i, --interactive |
Drop into a shell inside the container |
-c, --clean |
Delete temp files after the run |
-t, --timeout SECS |
Override timeout (also: GSLOCAL_TIMEOUT env var) |
-v, --verbose |
Show full build and Docker output |
Timeout precedence: --timeout flag > GSLOCAL_TIMEOUT env var > gslocal.toml value > 60s default.
After each run, the following are available for inspection:
.gslocal/
temp/
submission/ # prepared submission files
results/ # results.json
autograder/ # extracted autograder zip contents
gslocal init
Generate a gslocal.toml in the current directory. Prompts for each field with example hints. Skipping a required field writes a placeholder value that gslocal will catch and refuse to run with.
| Flag | Description |
|---|---|
--placeholders |
Write config with all sentinel values, no prompts |
gslocal clean
Remove local gslocal state for the current project.
| Flag | Description |
|---|---|
| (none) | Delete .gslocal/temp/ only |
--image |
Also remove the Docker image for this project |
--all |
Delete .gslocal/ entirely and remove Docker image (full reset) |
Project State
gslocal stores all state in a .gslocal/ directory at the project root. Add it to your .gitignore (gslocal init offers to do this automatically):
.gslocal/
Adding gslocal to an Existing Autograder
- Install gslocal:
pip install gslocal - Run
gslocal initin your autograder project root - Fill in your build command, zip output path, and the files to watch for changes
- Add
.gslocal/to.gitignore - Run
gslocal run <submission>to test
Contributing
Contributions are welcome. See CONTRIBUTING.md for setup instructions, project layout, and pull request guidelines.
Releasing
gslocal is released through the GitHub Actions workflow at .github/workflows/publish.yml.
Before cutting a release:
- Update
versioninpyproject.toml - Add a matching section to
CHANGELOG.md, for example## [0.1.1] - 2026-05-02 - Merge the release-ready changes to
main
Then, in GitHub:
- Open
Actions - Select
Release and Publish - Click
Run workflow - Enter the version number, such as
0.1.1
The workflow will:
- verify the run is on
main - verify the requested version matches
pyproject.toml - verify
CHANGELOG.mdcontains a section for that version - fail if that version is already published on PyPI
- build the package and run
twine check - create the
v<version>tag if needed - create the GitHub Release from the matching changelog section if needed
- publish the package to PyPI
If the GitHub tag or release is created but PyPI publishing fails, fix the issue and rerun the workflow for the same version. If PyPI already has that version, publish a new patch version instead.
License
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file gslocal-0.1.1.tar.gz.
File metadata
- Download URL: gslocal-0.1.1.tar.gz
- Upload date:
- Size: 19.8 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
3243cdfec6541f96160e126c4c0fa9cf87ee131931a72dbe396e1a226deeeab4
|
|
| MD5 |
64a3d7de8cfa3803c7c765c295344186
|
|
| BLAKE2b-256 |
0d35004a5119c78659b349a91eabe8053d6e69a275c18d5eb9e6421cc4959501
|
Provenance
The following attestation bundles were made for gslocal-0.1.1.tar.gz:
Publisher:
publish.yml on naasanov/gslocal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gslocal-0.1.1.tar.gz -
Subject digest:
3243cdfec6541f96160e126c4c0fa9cf87ee131931a72dbe396e1a226deeeab4 - Sigstore transparency entry: 1853882954
- Sigstore integration time:
-
Permalink:
naasanov/gslocal@08c7f9b62826075bd76f2f0b40a6d7da421bb338 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/naasanov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@08c7f9b62826075bd76f2f0b40a6d7da421bb338 -
Trigger Event:
workflow_dispatch
-
Statement type:
File details
Details for the file gslocal-0.1.1-py3-none-any.whl.
File metadata
- Download URL: gslocal-0.1.1-py3-none-any.whl
- Upload date:
- Size: 21.2 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
9ee36980b343593b2ad43e40e143f1b167d34f856a7f97eb57afa66fe8651f32
|
|
| MD5 |
f0ffeca7dc236802b4b0f2e99150a98b
|
|
| BLAKE2b-256 |
20bd05693695a528b2f6a7e1fd1f161fac07d36efd6d7b10cd909611bd2468bd
|
Provenance
The following attestation bundles were made for gslocal-0.1.1-py3-none-any.whl:
Publisher:
publish.yml on naasanov/gslocal
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
gslocal-0.1.1-py3-none-any.whl -
Subject digest:
9ee36980b343593b2ad43e40e143f1b167d34f856a7f97eb57afa66fe8651f32 - Sigstore transparency entry: 1853882982
- Sigstore integration time:
-
Permalink:
naasanov/gslocal@08c7f9b62826075bd76f2f0b40a6d7da421bb338 -
Branch / Tag:
refs/heads/main - Owner: https://github.com/naasanov
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@08c7f9b62826075bd76f2f0b40a6d7da421bb338 -
Trigger Event:
workflow_dispatch
-
Statement type: