A lightweight autograder where tests run locally and data is stored remotely
Project description
PhiGrade
PhiGrade is a lightweight autograder framework designed for educational environments where tests run locally and results are stored remotely. It provides a unique approach to automated grading by executing student code locally while maintaining centralized result management.
Key Features
- Local Test Execution: Tests run on the student's machine, ensuring security and scalability
- Dual-Mode Operation: Teacher mode for storing references, student mode for comparison
- Multiple Comparison Types: Support for exact equality (
phigrade_isequal) and numpy array tolerance (phigrade_allclose) - Flexible Deployment: Local development server or remote backend integration
- Call Count Tracking: Multiple assertions per test function with unique identification
Quick Start
Installation
# Install with Poetry
poetry install
# Or with pip
pip install -e .
Basic Usage
- Create a configuration file (
phigrade.yaml):
assignment_id: "homework_1"
use_local_server: true
teacher_mode: false
timeout_seconds: 5
- Write test functions:
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
import numpy as np
@weight(5.0)
def test_basic_math():
result = 2 + 2
phigrade_isequal(result) # Compares against stored reference
return result
@weight(10.0)
def test_numpy_array():
result = np.array([1.0, 2.0, 3.0])
phigrade_allclose(result, rtol=1e-5, atol=1e-8)
return result
- Run tests:
poetry run python your_test_file.py
Architecture
Dual-Mode Workflow
Teacher Mode (teacher_mode: true):
- Instructors run tests on reference implementations
- Results are stored as "correct" references with comparison metadata
- Each test function with
@weightdecorator stores its output
Student Mode (teacher_mode: false):
- Students run the same test functions on their implementations
- Their outputs are compared against stored references
- Automatic scoring based on comparison results
Comparison Types
Exact Equality (phigrade_isequal)
@weight(3.0)
def test_string_processing():
result = process_text("hello")
phigrade_isequal(result) # Exact match required
Numpy Array Tolerance (phigrade_allclose)
@weight(8.0)
def test_numerical_computation():
result = np.array([1.0, 2.0, 3.0])
phigrade_allclose(result, rtol=1e-5, atol=1e-8, equal_nan=False)
Multiple Calls Per Test
@weight(15.0)
def test_multiple_operations():
# Each call gets unique identifier: test_multiple_operations@0, @1, @2
result1 = operation_a()
phigrade_isequal(result1)
result2 = operation_b()
phigrade_isequal(result2)
array_result = operation_c()
phigrade_allclose(array_result, rtol=0.01)
Configuration
Local Development Mode
assignment_id: "my_assignment"
use_local_server: true
teacher_mode: false
timeout_seconds: 10
Gradescope JSON Output
PhiGrade can generate a Gradescope-compatible JSON file in student mode by
setting gradescope_json_file in the configuration. When this option is set,
PhiGrade automatically writes the JSON after each test function finishes.
gradescope_json_file: "results.json"
Notes about output capture:
- PhiGrade captures a combined stdout/stderr stream from within each
@weighttest function whengradescope_json_fileis set andteacher_modeis false. - The captured output is appended to the Gradescope test entry under
output. - This capture is Python-stream only and does not include subprocess output.
Remote Backend Mode
assignment_id: "my_assignment"
use_local_server: false
server_url: "https://your-phigrade-backend.com"
api_key: "your_api_key_here"
teacher_mode: false
timeout_seconds: 30
Data Flow
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
│ Teacher Mode │ │ │ │ Student Mode │
│ │ │ PhiGrade Server │ │ │
│ Store Reference │───▶│ │◀───│ Submit Results │
│ Results │ │ (Local/Remote) │ │ Get Comparison │
└─────────────────┘ └──────────────────┘ └─────────────────┘
- Teacher Phase: Instructor runs tests, stores reference outputs and comparison configuration
- Student Phase: Students run tests, submit outputs for comparison against references
- Scoring: Automatic scoring based on comparison results (exact match or tolerance-based)
Development
Running Tests
# Run all tests
poetry run pytest
# Run specific test categories
poetry run pytest tests/test_phigrade_isequal_local.py # Equality tests
poetry run pytest tests/test_phigrade_allclose_local.py # Array tolerance tests
poetry run pytest tests/test_local_server.py # Local server tests
poetry run pytest tests/test_real_backend_integration.py # Backend integration
# Verbose output
poetry run pytest -v
Local Development Server
# Start local server manually
poetry run python -m phigrade.app
# Server starts automatically when use_local_server: true
Project Structure
phigrade/
├── phigrade/
│ ├── __init__.py
│ ├── phigrade.py # Main API (decorators, assertions)
│ ├── app.py # Local FastAPI server
│ ├── server.py # Server management
│ ├── compare.py # Comparison logic
│ └── config.py # Configuration handling
├── tests/
│ ├── test_phigrade_isequal_local.py # Equality comparison tests
│ ├── test_phigrade_allclose_local.py # Array tolerance tests
│ ├── test_local_server.py # Local server API tests
│ └── test_real_backend_integration.py # Backend integration tests
├── examples/ # Example usage
├── phigrade.yaml # Configuration file
└── README.md
Upload to pypi
poetry build --clean
twine upload dist/*
Examples
Complete Teacher Workflow
# teacher_tests.py
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
import numpy as np
@weight(5.0)
def test_factorial():
"""Test factorial function"""
result = factorial(5)
phigrade_isequal(result) # Stores 120 as reference
return result
@weight(10.0)
def test_matrix_multiplication():
"""Test matrix operations"""
A = np.array([[1, 2], [3, 4]])
B = np.array([[2, 0], [1, 2]])
result = A @ B
phigrade_allclose(result, rtol=1e-10) # Stores array with tight tolerance
return result
Complete Student Workflow
# student_tests.py (same function names)
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
import numpy as np
@weight(5.0)
def test_factorial():
"""Student implementation"""
result = my_factorial(5) # Student's implementation
phigrade_isequal(result) # Compares against teacher's stored result
return result
@weight(10.0)
def test_matrix_multiplication():
"""Student implementation"""
A = np.array([[1, 2], [3, 4]])
B = np.array([[2, 0], [1, 2]])
result = my_matrix_mult(A, B) # Student's implementation
phigrade_allclose(result, rtol=1e-10) # Compares with tolerance
return result
API Reference
Decorators
@weight(points: float)
Decorates test functions to assign point values and enable phigrade tracking.
Assertion Functions
phigrade_isequal(output: Any) -> None
Compares output using exact equality (==).
phigrade_allclose(output: np.ndarray, rtol=1e-05, atol=1e-08, equal_nan=False) -> None
Compares numpy arrays with tolerance using np.allclose().
Configuration
PhiGradeConfig
Loads configuration from YAML files with validation and type checking.
Contributing
- Fork the repository
- Create a feature branch
- Add tests for new functionality
- Ensure all tests pass:
poetry run pytest - Submit a pull request
License
This project is licensed under the MIT License - see the LICENSE file for details.
Related Projects
- phigrade-backend: Centralized backend server for multi-user deployments
- phigrade-frontend: Web interface for instructors and administrators
Project details
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 phigrade-2.1.2.tar.gz.
File metadata
- Download URL: phigrade-2.1.2.tar.gz
- Upload date:
- Size: 19.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
289efbc170fe6bdcf43f0eb3683fa163b2383d4593732deaea926d8719eee361
|
|
| MD5 |
6a200d6c3c4f8f90de5da93d740293d6
|
|
| BLAKE2b-256 |
dbdfa8fc883e9cc404665df89b7897017f209ffc05cc6a3b58026e579629023d
|
File details
Details for the file phigrade-2.1.2-py3-none-any.whl.
File metadata
- Download URL: phigrade-2.1.2-py3-none-any.whl
- Upload date:
- Size: 19.1 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.12.7
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
ac70da97d4987d1e2575c68a31f5ec90105d4dae3186d058113ab7d7b906eb4e
|
|
| MD5 |
92bac4640680b90a35a035f07c2f3431
|
|
| BLAKE2b-256 |
e9e94775582cdf62289ac3c5e02e22089bba55dcba1fe8f57920cbc01d820064
|