Skip to content

Commit 19fe61a

Browse files
committed
fix: more robust casting of int / float in grading policy (#275)
1 parent 03a482a commit 19fe61a

2 files changed

Lines changed: 42 additions & 5 deletions

File tree

lms/djangoapps/course_home_api/progress/api.py

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,34 @@
1717
User = get_user_model()
1818

1919

20+
def _to_float(value, default=0.0) -> float:
21+
"""Parse a grading policy value as a float, returning default for any unparseable input."""
22+
try:
23+
return float(value)
24+
except (TypeError, ValueError):
25+
return float(default)
26+
27+
28+
def _to_int(value, default=0) -> int:
29+
"""
30+
Parse a grading policy value as an int, returning default for non-integer or unparseable input.
31+
32+
Accepts string representations of both integers ('2') and whole-number floats ('2.0').
33+
Returns default for non-finite values (nan/inf), fractional floats ('2.9'), and
34+
anything that cannot be parsed as a number.
35+
"""
36+
try:
37+
parsed = float(value)
38+
except (TypeError, ValueError):
39+
return default
40+
if not parsed.is_integer():
41+
return default
42+
try:
43+
return int(parsed)
44+
except (OverflowError, ValueError):
45+
return default
46+
47+
2048
@dataclass
2149
class _AssignmentBucket:
2250
"""Holds scores and visibility info for one assignment type.
@@ -130,10 +158,10 @@ def _build_policy_map(self) -> dict:
130158
policy_map = {}
131159
for policy in self.grading_policy.get('GRADER', []):
132160
policy_map[policy.get('type')] = {
133-
'weight': float(policy.get('weight', 0.0) or 0.0),
161+
'weight': _to_float(policy.get('weight')),
134162
'short_label': policy.get('short_label', ''),
135-
'num_droppable': int(policy.get('drop_count', 0) or 0),
136-
'num_total': int(policy.get('min_count', 0) or 0),
163+
'num_droppable': _to_int(policy.get('drop_count')),
164+
'num_total': _to_int(policy.get('min_count')),
137165
}
138166
return policy_map
139167

lms/djangoapps/course_home_api/progress/tests/test_api.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,14 +88,23 @@ def _make_subsection(fmt, earned, possible, show_corr, *, due_delta_days=None, i
8888
{'avg': 0.0, 'weighted': 0.0, 'hidden': 'all', 'final': 0.0, 'last_grade_publish_date_days': 7},
8989
),
9090
(
91-
'string_typed_policy_counts',
91+
'string_int_typed_policy_counts',
9292
{'type': 'Homework', 'weight': '1.0', 'drop_count': '1', 'min_count': '2', 'short_label': 'HW'},
9393
[
9494
_make_subsection('Homework', 1, 1, ShowCorrectness.ALWAYS),
9595
_make_subsection('Homework', 0, 1, ShowCorrectness.ALWAYS),
9696
],
9797
{'avg': 1.0, 'weighted': 1.0, 'hidden': 'none', 'final': 1.0},
9898
),
99+
(
100+
'string_float_typed_policy_counts',
101+
{'type': 'Homework', 'weight': '1.0', 'drop_count': '1.0', 'min_count': '2.0', 'short_label': 'HW'},
102+
[
103+
_make_subsection('Homework', 1, 1, ShowCorrectness.ALWAYS),
104+
_make_subsection('Homework', 0, 1, ShowCorrectness.ALWAYS),
105+
],
106+
{'avg': 1.0, 'weighted': 1.0, 'hidden': 'none', 'final': 1.0},
107+
),
99108
]
100109

101110

@@ -196,7 +205,7 @@ def test_aggregate_assignment_type_grade_summary_scenarios(self):
196205
assert row['average_grade'] == expected['avg']
197206
assert row['weighted_grade'] == expected['weighted']
198207
assert row['has_hidden_contribution'] == expected['hidden']
199-
assert row['num_droppable'] == int(policy['drop_count'])
208+
assert row['num_droppable'] == int(float(policy['drop_count']))
200209
assert (row['last_grade_publish_date'] is not None) == (
201210
'last_grade_publish_date_days' in expected
202211
)

0 commit comments

Comments
 (0)