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 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.
@@ -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
@@ -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
+ ]