phigrade 1.0.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- phigrade-1.0.0/LICENSE +7 -0
- phigrade-1.0.0/PKG-INFO +300 -0
- phigrade-1.0.0/README.md +276 -0
- phigrade-1.0.0/phigrade/__init__.py +17 -0
- phigrade-1.0.0/phigrade/app.py +407 -0
- phigrade-1.0.0/phigrade/compare.py +49 -0
- phigrade-1.0.0/phigrade/gradescope_output.py +111 -0
- phigrade-1.0.0/phigrade/phigrade.py +317 -0
- phigrade-1.0.0/phigrade/server.py +101 -0
- phigrade-1.0.0/pyproject.toml +38 -0
phigrade-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
Copyright 2026 Matt Gormley, Jacob Rast, and contributors to this project
|
|
2
|
+
|
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
|
|
4
|
+
|
|
5
|
+
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
|
|
6
|
+
|
|
7
|
+
THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
phigrade-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,300 @@
|
|
|
1
|
+
Metadata-Version: 2.3
|
|
2
|
+
Name: phigrade
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: A lightweight autograder where tests run locally and data is stored remotely
|
|
5
|
+
License: MIT
|
|
6
|
+
Keywords: autograder,education,grading
|
|
7
|
+
Author: Matt Gormley
|
|
8
|
+
Author-email: mgormley@cs.cmu.edu
|
|
9
|
+
Requires-Python: >=3.12,<4.0
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Provides-Extra: dev
|
|
14
|
+
Requires-Dist: fastapi (>=0.115.13,<0.116.0)
|
|
15
|
+
Requires-Dist: numpy (>=2.0.0,<3.0.0)
|
|
16
|
+
Requires-Dist: omegaconf (>=2.3.0,<3.0.0)
|
|
17
|
+
Requires-Dist: pydantic (>=2.11.7,<3.0.0)
|
|
18
|
+
Requires-Dist: pytest (>=8.4.1,<9.0.0) ; extra == "dev"
|
|
19
|
+
Requires-Dist: requests (>=2.32.4,<3.0.0)
|
|
20
|
+
Requires-Dist: tinydb (>=4.8.2,<5.0.0)
|
|
21
|
+
Requires-Dist: uvicorn (>=0.34.3,<0.35.0)
|
|
22
|
+
Project-URL: Repository, https://github.com/mld-instructors/phigrade
|
|
23
|
+
Description-Content-Type: text/markdown
|
|
24
|
+
|
|
25
|
+
# PhiGrade
|
|
26
|
+
|
|
27
|
+
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.
|
|
28
|
+
|
|
29
|
+
## Key Features
|
|
30
|
+
|
|
31
|
+
- **Local Test Execution**: Tests run on the student's machine, ensuring security and scalability
|
|
32
|
+
- **Dual-Mode Operation**: Teacher mode for storing references, student mode for comparison
|
|
33
|
+
- **Multiple Comparison Types**: Support for exact equality (`phigrade_isequal`) and numpy array tolerance (`phigrade_allclose`)
|
|
34
|
+
- **Flexible Deployment**: Local development server or remote backend integration
|
|
35
|
+
- **Call Count Tracking**: Multiple assertions per test function with unique identification
|
|
36
|
+
|
|
37
|
+
## Quick Start
|
|
38
|
+
|
|
39
|
+
### Installation
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
# Install with Poetry
|
|
43
|
+
poetry install
|
|
44
|
+
|
|
45
|
+
# Or with pip
|
|
46
|
+
pip install -e .
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
### Basic Usage
|
|
50
|
+
|
|
51
|
+
1. **Create a configuration file** (`phigrade.yaml`):
|
|
52
|
+
```yaml
|
|
53
|
+
assignment_id: "homework_1"
|
|
54
|
+
use_local_server: true
|
|
55
|
+
teacher_mode: false
|
|
56
|
+
timeout_seconds: 5
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
2. **Write test functions**:
|
|
60
|
+
```python
|
|
61
|
+
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
|
|
62
|
+
import numpy as np
|
|
63
|
+
|
|
64
|
+
@weight(5.0)
|
|
65
|
+
def test_basic_math():
|
|
66
|
+
result = 2 + 2
|
|
67
|
+
phigrade_isequal(result) # Compares against stored reference
|
|
68
|
+
return result
|
|
69
|
+
|
|
70
|
+
@weight(10.0)
|
|
71
|
+
def test_numpy_array():
|
|
72
|
+
result = np.array([1.0, 2.0, 3.0])
|
|
73
|
+
phigrade_allclose(result, rtol=1e-5, atol=1e-8)
|
|
74
|
+
return result
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
3. **Run tests**:
|
|
78
|
+
```bash
|
|
79
|
+
poetry run python your_test_file.py
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Architecture
|
|
83
|
+
|
|
84
|
+
### Dual-Mode Workflow
|
|
85
|
+
|
|
86
|
+
**Teacher Mode** (`teacher_mode: true`):
|
|
87
|
+
- Instructors run tests on reference implementations
|
|
88
|
+
- Results are stored as "correct" references with comparison metadata
|
|
89
|
+
- Each test function with `@weight` decorator stores its output
|
|
90
|
+
|
|
91
|
+
**Student Mode** (`teacher_mode: false`):
|
|
92
|
+
- Students run the same test functions on their implementations
|
|
93
|
+
- Their outputs are compared against stored references
|
|
94
|
+
- Automatic scoring based on comparison results
|
|
95
|
+
|
|
96
|
+
### Comparison Types
|
|
97
|
+
|
|
98
|
+
#### Exact Equality (`phigrade_isequal`)
|
|
99
|
+
```python
|
|
100
|
+
@weight(3.0)
|
|
101
|
+
def test_string_processing():
|
|
102
|
+
result = process_text("hello")
|
|
103
|
+
phigrade_isequal(result) # Exact match required
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
#### Numpy Array Tolerance (`phigrade_allclose`)
|
|
107
|
+
```python
|
|
108
|
+
@weight(8.0)
|
|
109
|
+
def test_numerical_computation():
|
|
110
|
+
result = np.array([1.0, 2.0, 3.0])
|
|
111
|
+
phigrade_allclose(result, rtol=1e-5, atol=1e-8, equal_nan=False)
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
### Multiple Calls Per Test
|
|
115
|
+
|
|
116
|
+
```python
|
|
117
|
+
@weight(15.0)
|
|
118
|
+
def test_multiple_operations():
|
|
119
|
+
# Each call gets unique identifier: test_multiple_operations@0, @1, @2
|
|
120
|
+
result1 = operation_a()
|
|
121
|
+
phigrade_isequal(result1)
|
|
122
|
+
|
|
123
|
+
result2 = operation_b()
|
|
124
|
+
phigrade_isequal(result2)
|
|
125
|
+
|
|
126
|
+
array_result = operation_c()
|
|
127
|
+
phigrade_allclose(array_result, rtol=0.01)
|
|
128
|
+
```
|
|
129
|
+
|
|
130
|
+
## Configuration
|
|
131
|
+
|
|
132
|
+
### Local Development Mode
|
|
133
|
+
|
|
134
|
+
```yaml
|
|
135
|
+
assignment_id: "my_assignment"
|
|
136
|
+
use_local_server: true
|
|
137
|
+
teacher_mode: false
|
|
138
|
+
timeout_seconds: 10
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### Remote Backend Mode
|
|
142
|
+
|
|
143
|
+
```yaml
|
|
144
|
+
assignment_id: "my_assignment"
|
|
145
|
+
use_local_server: false
|
|
146
|
+
server_url: "https://your-phigrade-backend.com"
|
|
147
|
+
api_key: "your_api_key_here"
|
|
148
|
+
teacher_mode: false
|
|
149
|
+
timeout_seconds: 30
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
## Data Flow
|
|
153
|
+
|
|
154
|
+
```
|
|
155
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
156
|
+
│ Teacher Mode │ │ │ │ Student Mode │
|
|
157
|
+
│ │ │ PhiGrade Server │ │ │
|
|
158
|
+
│ Store Reference │───▶│ │◀───│ Submit Results │
|
|
159
|
+
│ Results │ │ (Local/Remote) │ │ Get Comparison │
|
|
160
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
1. **Teacher Phase**: Instructor runs tests, stores reference outputs and comparison configuration
|
|
164
|
+
2. **Student Phase**: Students run tests, submit outputs for comparison against references
|
|
165
|
+
3. **Scoring**: Automatic scoring based on comparison results (exact match or tolerance-based)
|
|
166
|
+
|
|
167
|
+
## Development
|
|
168
|
+
|
|
169
|
+
### Running Tests
|
|
170
|
+
|
|
171
|
+
```bash
|
|
172
|
+
# Run all tests
|
|
173
|
+
poetry run pytest
|
|
174
|
+
|
|
175
|
+
# Run specific test categories
|
|
176
|
+
poetry run pytest tests/test_phigrade_isequal_local.py # Equality tests
|
|
177
|
+
poetry run pytest tests/test_phigrade_allclose_local.py # Array tolerance tests
|
|
178
|
+
poetry run pytest tests/test_local_server.py # Local server tests
|
|
179
|
+
poetry run pytest tests/test_real_backend_integration.py # Backend integration
|
|
180
|
+
|
|
181
|
+
# Verbose output
|
|
182
|
+
poetry run pytest -v
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
### Local Development Server
|
|
186
|
+
|
|
187
|
+
```bash
|
|
188
|
+
# Start local server manually
|
|
189
|
+
poetry run python -m phigrade.app
|
|
190
|
+
|
|
191
|
+
# Server starts automatically when use_local_server: true
|
|
192
|
+
```
|
|
193
|
+
|
|
194
|
+
### Project Structure
|
|
195
|
+
|
|
196
|
+
```
|
|
197
|
+
phigrade/
|
|
198
|
+
├── phigrade/
|
|
199
|
+
│ ├── __init__.py
|
|
200
|
+
│ ├── phigrade.py # Main API (decorators, assertions)
|
|
201
|
+
│ ├── app.py # Local FastAPI server
|
|
202
|
+
│ ├── server.py # Server management
|
|
203
|
+
│ ├── compare.py # Comparison logic
|
|
204
|
+
│ └── config.py # Configuration handling
|
|
205
|
+
├── tests/
|
|
206
|
+
│ ├── test_phigrade_isequal_local.py # Equality comparison tests
|
|
207
|
+
│ ├── test_phigrade_allclose_local.py # Array tolerance tests
|
|
208
|
+
│ ├── test_local_server.py # Local server API tests
|
|
209
|
+
│ └── test_real_backend_integration.py # Backend integration tests
|
|
210
|
+
├── examples/ # Example usage
|
|
211
|
+
├── phigrade.yaml # Configuration file
|
|
212
|
+
└── README.md
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
## Examples
|
|
216
|
+
|
|
217
|
+
### Complete Teacher Workflow
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# teacher_tests.py
|
|
221
|
+
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
|
|
222
|
+
import numpy as np
|
|
223
|
+
|
|
224
|
+
@weight(5.0)
|
|
225
|
+
def test_factorial():
|
|
226
|
+
"""Test factorial function"""
|
|
227
|
+
result = factorial(5)
|
|
228
|
+
phigrade_isequal(result) # Stores 120 as reference
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
@weight(10.0)
|
|
232
|
+
def test_matrix_multiplication():
|
|
233
|
+
"""Test matrix operations"""
|
|
234
|
+
A = np.array([[1, 2], [3, 4]])
|
|
235
|
+
B = np.array([[2, 0], [1, 2]])
|
|
236
|
+
result = A @ B
|
|
237
|
+
phigrade_allclose(result, rtol=1e-10) # Stores array with tight tolerance
|
|
238
|
+
return result
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
### Complete Student Workflow
|
|
242
|
+
|
|
243
|
+
```python
|
|
244
|
+
# student_tests.py (same function names)
|
|
245
|
+
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
|
|
246
|
+
import numpy as np
|
|
247
|
+
|
|
248
|
+
@weight(5.0)
|
|
249
|
+
def test_factorial():
|
|
250
|
+
"""Student implementation"""
|
|
251
|
+
result = my_factorial(5) # Student's implementation
|
|
252
|
+
phigrade_isequal(result) # Compares against teacher's stored result
|
|
253
|
+
return result
|
|
254
|
+
|
|
255
|
+
@weight(10.0)
|
|
256
|
+
def test_matrix_multiplication():
|
|
257
|
+
"""Student implementation"""
|
|
258
|
+
A = np.array([[1, 2], [3, 4]])
|
|
259
|
+
B = np.array([[2, 0], [1, 2]])
|
|
260
|
+
result = my_matrix_mult(A, B) # Student's implementation
|
|
261
|
+
phigrade_allclose(result, rtol=1e-10) # Compares with tolerance
|
|
262
|
+
return result
|
|
263
|
+
```
|
|
264
|
+
|
|
265
|
+
## API Reference
|
|
266
|
+
|
|
267
|
+
### Decorators
|
|
268
|
+
|
|
269
|
+
#### `@weight(points: float)`
|
|
270
|
+
Decorates test functions to assign point values and enable phigrade tracking.
|
|
271
|
+
|
|
272
|
+
### Assertion Functions
|
|
273
|
+
|
|
274
|
+
#### `phigrade_isequal(output: Any) -> None`
|
|
275
|
+
Compares output using exact equality (`==`).
|
|
276
|
+
|
|
277
|
+
#### `phigrade_allclose(output: np.ndarray, rtol=1e-05, atol=1e-08, equal_nan=False) -> None`
|
|
278
|
+
Compares numpy arrays with tolerance using `np.allclose()`.
|
|
279
|
+
|
|
280
|
+
### Configuration
|
|
281
|
+
|
|
282
|
+
#### `PhiGradeConfig`
|
|
283
|
+
Loads configuration from YAML files with validation and type checking.
|
|
284
|
+
|
|
285
|
+
## Contributing
|
|
286
|
+
|
|
287
|
+
1. Fork the repository
|
|
288
|
+
2. Create a feature branch
|
|
289
|
+
3. Add tests for new functionality
|
|
290
|
+
4. Ensure all tests pass: `poetry run pytest`
|
|
291
|
+
5. Submit a pull request
|
|
292
|
+
|
|
293
|
+
## License
|
|
294
|
+
|
|
295
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
296
|
+
|
|
297
|
+
## Related Projects
|
|
298
|
+
|
|
299
|
+
- **phigrade-backend**: Centralized backend server for multi-user deployments
|
|
300
|
+
- **phigrade-frontend**: Web interface for instructors and administrators
|
phigrade-1.0.0/README.md
ADDED
|
@@ -0,0 +1,276 @@
|
|
|
1
|
+
# PhiGrade
|
|
2
|
+
|
|
3
|
+
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.
|
|
4
|
+
|
|
5
|
+
## Key Features
|
|
6
|
+
|
|
7
|
+
- **Local Test Execution**: Tests run on the student's machine, ensuring security and scalability
|
|
8
|
+
- **Dual-Mode Operation**: Teacher mode for storing references, student mode for comparison
|
|
9
|
+
- **Multiple Comparison Types**: Support for exact equality (`phigrade_isequal`) and numpy array tolerance (`phigrade_allclose`)
|
|
10
|
+
- **Flexible Deployment**: Local development server or remote backend integration
|
|
11
|
+
- **Call Count Tracking**: Multiple assertions per test function with unique identification
|
|
12
|
+
|
|
13
|
+
## Quick Start
|
|
14
|
+
|
|
15
|
+
### Installation
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
# Install with Poetry
|
|
19
|
+
poetry install
|
|
20
|
+
|
|
21
|
+
# Or with pip
|
|
22
|
+
pip install -e .
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
### Basic Usage
|
|
26
|
+
|
|
27
|
+
1. **Create a configuration file** (`phigrade.yaml`):
|
|
28
|
+
```yaml
|
|
29
|
+
assignment_id: "homework_1"
|
|
30
|
+
use_local_server: true
|
|
31
|
+
teacher_mode: false
|
|
32
|
+
timeout_seconds: 5
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
2. **Write test functions**:
|
|
36
|
+
```python
|
|
37
|
+
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
|
|
38
|
+
import numpy as np
|
|
39
|
+
|
|
40
|
+
@weight(5.0)
|
|
41
|
+
def test_basic_math():
|
|
42
|
+
result = 2 + 2
|
|
43
|
+
phigrade_isequal(result) # Compares against stored reference
|
|
44
|
+
return result
|
|
45
|
+
|
|
46
|
+
@weight(10.0)
|
|
47
|
+
def test_numpy_array():
|
|
48
|
+
result = np.array([1.0, 2.0, 3.0])
|
|
49
|
+
phigrade_allclose(result, rtol=1e-5, atol=1e-8)
|
|
50
|
+
return result
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
3. **Run tests**:
|
|
54
|
+
```bash
|
|
55
|
+
poetry run python your_test_file.py
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Architecture
|
|
59
|
+
|
|
60
|
+
### Dual-Mode Workflow
|
|
61
|
+
|
|
62
|
+
**Teacher Mode** (`teacher_mode: true`):
|
|
63
|
+
- Instructors run tests on reference implementations
|
|
64
|
+
- Results are stored as "correct" references with comparison metadata
|
|
65
|
+
- Each test function with `@weight` decorator stores its output
|
|
66
|
+
|
|
67
|
+
**Student Mode** (`teacher_mode: false`):
|
|
68
|
+
- Students run the same test functions on their implementations
|
|
69
|
+
- Their outputs are compared against stored references
|
|
70
|
+
- Automatic scoring based on comparison results
|
|
71
|
+
|
|
72
|
+
### Comparison Types
|
|
73
|
+
|
|
74
|
+
#### Exact Equality (`phigrade_isequal`)
|
|
75
|
+
```python
|
|
76
|
+
@weight(3.0)
|
|
77
|
+
def test_string_processing():
|
|
78
|
+
result = process_text("hello")
|
|
79
|
+
phigrade_isequal(result) # Exact match required
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
#### Numpy Array Tolerance (`phigrade_allclose`)
|
|
83
|
+
```python
|
|
84
|
+
@weight(8.0)
|
|
85
|
+
def test_numerical_computation():
|
|
86
|
+
result = np.array([1.0, 2.0, 3.0])
|
|
87
|
+
phigrade_allclose(result, rtol=1e-5, atol=1e-8, equal_nan=False)
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Multiple Calls Per Test
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
@weight(15.0)
|
|
94
|
+
def test_multiple_operations():
|
|
95
|
+
# Each call gets unique identifier: test_multiple_operations@0, @1, @2
|
|
96
|
+
result1 = operation_a()
|
|
97
|
+
phigrade_isequal(result1)
|
|
98
|
+
|
|
99
|
+
result2 = operation_b()
|
|
100
|
+
phigrade_isequal(result2)
|
|
101
|
+
|
|
102
|
+
array_result = operation_c()
|
|
103
|
+
phigrade_allclose(array_result, rtol=0.01)
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
## Configuration
|
|
107
|
+
|
|
108
|
+
### Local Development Mode
|
|
109
|
+
|
|
110
|
+
```yaml
|
|
111
|
+
assignment_id: "my_assignment"
|
|
112
|
+
use_local_server: true
|
|
113
|
+
teacher_mode: false
|
|
114
|
+
timeout_seconds: 10
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### Remote Backend Mode
|
|
118
|
+
|
|
119
|
+
```yaml
|
|
120
|
+
assignment_id: "my_assignment"
|
|
121
|
+
use_local_server: false
|
|
122
|
+
server_url: "https://your-phigrade-backend.com"
|
|
123
|
+
api_key: "your_api_key_here"
|
|
124
|
+
teacher_mode: false
|
|
125
|
+
timeout_seconds: 30
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
## Data Flow
|
|
129
|
+
|
|
130
|
+
```
|
|
131
|
+
┌─────────────────┐ ┌──────────────────┐ ┌─────────────────┐
|
|
132
|
+
│ Teacher Mode │ │ │ │ Student Mode │
|
|
133
|
+
│ │ │ PhiGrade Server │ │ │
|
|
134
|
+
│ Store Reference │───▶│ │◀───│ Submit Results │
|
|
135
|
+
│ Results │ │ (Local/Remote) │ │ Get Comparison │
|
|
136
|
+
└─────────────────┘ └──────────────────┘ └─────────────────┘
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
1. **Teacher Phase**: Instructor runs tests, stores reference outputs and comparison configuration
|
|
140
|
+
2. **Student Phase**: Students run tests, submit outputs for comparison against references
|
|
141
|
+
3. **Scoring**: Automatic scoring based on comparison results (exact match or tolerance-based)
|
|
142
|
+
|
|
143
|
+
## Development
|
|
144
|
+
|
|
145
|
+
### Running Tests
|
|
146
|
+
|
|
147
|
+
```bash
|
|
148
|
+
# Run all tests
|
|
149
|
+
poetry run pytest
|
|
150
|
+
|
|
151
|
+
# Run specific test categories
|
|
152
|
+
poetry run pytest tests/test_phigrade_isequal_local.py # Equality tests
|
|
153
|
+
poetry run pytest tests/test_phigrade_allclose_local.py # Array tolerance tests
|
|
154
|
+
poetry run pytest tests/test_local_server.py # Local server tests
|
|
155
|
+
poetry run pytest tests/test_real_backend_integration.py # Backend integration
|
|
156
|
+
|
|
157
|
+
# Verbose output
|
|
158
|
+
poetry run pytest -v
|
|
159
|
+
```
|
|
160
|
+
|
|
161
|
+
### Local Development Server
|
|
162
|
+
|
|
163
|
+
```bash
|
|
164
|
+
# Start local server manually
|
|
165
|
+
poetry run python -m phigrade.app
|
|
166
|
+
|
|
167
|
+
# Server starts automatically when use_local_server: true
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
### Project Structure
|
|
171
|
+
|
|
172
|
+
```
|
|
173
|
+
phigrade/
|
|
174
|
+
├── phigrade/
|
|
175
|
+
│ ├── __init__.py
|
|
176
|
+
│ ├── phigrade.py # Main API (decorators, assertions)
|
|
177
|
+
│ ├── app.py # Local FastAPI server
|
|
178
|
+
│ ├── server.py # Server management
|
|
179
|
+
│ ├── compare.py # Comparison logic
|
|
180
|
+
│ └── config.py # Configuration handling
|
|
181
|
+
├── tests/
|
|
182
|
+
│ ├── test_phigrade_isequal_local.py # Equality comparison tests
|
|
183
|
+
│ ├── test_phigrade_allclose_local.py # Array tolerance tests
|
|
184
|
+
│ ├── test_local_server.py # Local server API tests
|
|
185
|
+
│ └── test_real_backend_integration.py # Backend integration tests
|
|
186
|
+
├── examples/ # Example usage
|
|
187
|
+
├── phigrade.yaml # Configuration file
|
|
188
|
+
└── README.md
|
|
189
|
+
```
|
|
190
|
+
|
|
191
|
+
## Examples
|
|
192
|
+
|
|
193
|
+
### Complete Teacher Workflow
|
|
194
|
+
|
|
195
|
+
```python
|
|
196
|
+
# teacher_tests.py
|
|
197
|
+
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
|
|
198
|
+
import numpy as np
|
|
199
|
+
|
|
200
|
+
@weight(5.0)
|
|
201
|
+
def test_factorial():
|
|
202
|
+
"""Test factorial function"""
|
|
203
|
+
result = factorial(5)
|
|
204
|
+
phigrade_isequal(result) # Stores 120 as reference
|
|
205
|
+
return result
|
|
206
|
+
|
|
207
|
+
@weight(10.0)
|
|
208
|
+
def test_matrix_multiplication():
|
|
209
|
+
"""Test matrix operations"""
|
|
210
|
+
A = np.array([[1, 2], [3, 4]])
|
|
211
|
+
B = np.array([[2, 0], [1, 2]])
|
|
212
|
+
result = A @ B
|
|
213
|
+
phigrade_allclose(result, rtol=1e-10) # Stores array with tight tolerance
|
|
214
|
+
return result
|
|
215
|
+
```
|
|
216
|
+
|
|
217
|
+
### Complete Student Workflow
|
|
218
|
+
|
|
219
|
+
```python
|
|
220
|
+
# student_tests.py (same function names)
|
|
221
|
+
from phigrade.phigrade import phigrade_isequal, phigrade_allclose, weight
|
|
222
|
+
import numpy as np
|
|
223
|
+
|
|
224
|
+
@weight(5.0)
|
|
225
|
+
def test_factorial():
|
|
226
|
+
"""Student implementation"""
|
|
227
|
+
result = my_factorial(5) # Student's implementation
|
|
228
|
+
phigrade_isequal(result) # Compares against teacher's stored result
|
|
229
|
+
return result
|
|
230
|
+
|
|
231
|
+
@weight(10.0)
|
|
232
|
+
def test_matrix_multiplication():
|
|
233
|
+
"""Student implementation"""
|
|
234
|
+
A = np.array([[1, 2], [3, 4]])
|
|
235
|
+
B = np.array([[2, 0], [1, 2]])
|
|
236
|
+
result = my_matrix_mult(A, B) # Student's implementation
|
|
237
|
+
phigrade_allclose(result, rtol=1e-10) # Compares with tolerance
|
|
238
|
+
return result
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
## API Reference
|
|
242
|
+
|
|
243
|
+
### Decorators
|
|
244
|
+
|
|
245
|
+
#### `@weight(points: float)`
|
|
246
|
+
Decorates test functions to assign point values and enable phigrade tracking.
|
|
247
|
+
|
|
248
|
+
### Assertion Functions
|
|
249
|
+
|
|
250
|
+
#### `phigrade_isequal(output: Any) -> None`
|
|
251
|
+
Compares output using exact equality (`==`).
|
|
252
|
+
|
|
253
|
+
#### `phigrade_allclose(output: np.ndarray, rtol=1e-05, atol=1e-08, equal_nan=False) -> None`
|
|
254
|
+
Compares numpy arrays with tolerance using `np.allclose()`.
|
|
255
|
+
|
|
256
|
+
### Configuration
|
|
257
|
+
|
|
258
|
+
#### `PhiGradeConfig`
|
|
259
|
+
Loads configuration from YAML files with validation and type checking.
|
|
260
|
+
|
|
261
|
+
## Contributing
|
|
262
|
+
|
|
263
|
+
1. Fork the repository
|
|
264
|
+
2. Create a feature branch
|
|
265
|
+
3. Add tests for new functionality
|
|
266
|
+
4. Ensure all tests pass: `poetry run pytest`
|
|
267
|
+
5. Submit a pull request
|
|
268
|
+
|
|
269
|
+
## License
|
|
270
|
+
|
|
271
|
+
This project is licensed under the MIT License - see the LICENSE file for details.
|
|
272
|
+
|
|
273
|
+
## Related Projects
|
|
274
|
+
|
|
275
|
+
- **phigrade-backend**: Centralized backend server for multi-user deployments
|
|
276
|
+
- **phigrade-frontend**: Web interface for instructors and administrators
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# Import all functions from phigrade module
|
|
2
|
+
from .phigrade import (
|
|
3
|
+
weight,
|
|
4
|
+
phigrade_allclose,
|
|
5
|
+
phigrade_isequal,
|
|
6
|
+
PhiGradeConfig,
|
|
7
|
+
load_phigrade_config,
|
|
8
|
+
)
|
|
9
|
+
|
|
10
|
+
# Make everything available at package level
|
|
11
|
+
__all__ = [
|
|
12
|
+
'weight',
|
|
13
|
+
'phigrade_allclose',
|
|
14
|
+
'phigrade_isequal',
|
|
15
|
+
'PhiGradeConfig',
|
|
16
|
+
'load_phigrade_config',
|
|
17
|
+
]
|