Skip to content

Commit

Permalink
Merge pull request #28 from lpm0073/next
Browse files Browse the repository at this point in the history
refactor: add pydantic. strongly type Grade class
  • Loading branch information
lpm0073 authored Nov 28, 2023
2 parents 41907b7 + ea2ea9c commit 28d4e74
Show file tree
Hide file tree
Showing 5 changed files with 53 additions and 9 deletions.
2 changes: 1 addition & 1 deletion grader/__version__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# -*- coding: utf-8 -*-
__version__ = "1.2.0"
__version__ = "1.3.0"
51 changes: 46 additions & 5 deletions grader/grader.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
import json
import os

from pydantic import BaseModel, field_validator, model_validator

from .exceptions import (
AGException,
IncorrectResponseTypeError,
Expand All @@ -13,6 +15,14 @@
)


VALID_MESSAGE_TYPES = [
"Success",
IncorrectResponseTypeError.__name__,
IncorrectResponseValueError.__name__,
InvalidResponseStructureError.__name__,
ResponseFailedError.__name__,
]

HERE = os.path.abspath(os.path.dirname(__file__))
REQUIRED_KEYS_SPEC = "required-keys.json"
REQUIRED_KEYS_PATH = os.path.join(HERE, "data", REQUIRED_KEYS_SPEC)
Expand All @@ -21,6 +31,39 @@
AI_RESPONSE = {"content": "a response from the AI", "additional_kwargs": {}, "type": "ai", "example": False}


class Grade(BaseModel):
"""
This is the base class for all Grader types. It provides the common interface and
functionality for grading, but does not implement the grading logic itself.
Subclasses should override the necessary methods to provide the grading logic.
"""

potential_points: float
grade: float
message: str
message_type: str

def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
self.model_validate(self)

@model_validator(mode="after")
def validate_grade(self) -> "Grade":
"""Validate that the grade is >= 0 and <= potential_points"""
if self.grade < 0:
raise ValueError(f"grade must be at least 0.00. received: {self.grade}")
if self.grade > self.potential_points:
raise ValueError(f"grade must be less than or equal to potential_points. received: {self.grade}")
return self

@field_validator("message_type")
def message_type_is_valid(self, message_type):
"""Validate that the message_type is valid"""
if message_type not in VALID_MESSAGE_TYPES:
raise ValueError(f"message_type must be one of {VALID_MESSAGE_TYPES}")
return message_type


# flake8: noqa: E701
class AutomatedGrader:
"""Grade a submission against an assignment."""
Expand Down Expand Up @@ -171,11 +214,9 @@ def grade_response(self, message: AGException = None):
grade = self.potential_points * (1 - (message.penalty_pct if message else 0))
message_type = message.__class__.__name__ if message else "Success"
message = str(message) if message else "Great job!"
return {
"grade": grade,
"message": message,
"message_type": message_type,
}

grade = Grade(grade=grade, message=message, message_type=message_type, potential_points=self.potential_points)
return grade.model_dump()

def grade(self):
"""Grade the assignment."""
Expand Down
4 changes: 2 additions & 2 deletions grader/tests/test_responses.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ def test_success(self):
assert isinstance(grade, dict), "The grade is not a dictionary"
assert grade["message_type"] == "Success"
assert "grade" in grade, "The dictionary does not contain the key 'grade'"
assert isinstance(grade["grade"], int), "The grade is not an int"
assert isinstance(grade["grade"], float), "The grade is not an float"
assert grade["grade"] == 100, "The grade is not 100"

assert "message" in grade, "The dictionary does not contain the key 'message'"
Expand All @@ -39,7 +39,7 @@ def test_success_verbose(self):
assert isinstance(grade, dict), "The grade is not a dictionary"
assert grade["message_type"] == "Success"
assert "grade" in grade, "The dictionary does not contain the key 'grade'"
assert isinstance(grade["grade"], int), "The grade is not an int"
assert isinstance(grade["grade"], float), "The grade is not an float"
assert grade["grade"] == 100, "The grade is not 100"

assert "message" in grade, "The dictionary does not contain the key 'message'"
Expand Down
3 changes: 3 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,3 +18,6 @@ pydocstringformatter==0.7.3
tox==4.11.3
codespell==2.2.6
python-dotenv==1.0.0

# package dependencies
pydantic==2.5.2
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,5 +15,5 @@
package_data={
"automated_grader": ["*.md"],
},
install_requires=[],
install_requires=["pydantic>=2.0.0"],
)

0 comments on commit 28d4e74

Please sign in to comment.