Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions src/pytest_prairielearn_grader/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -257,6 +257,29 @@ def get_builtins(builtin_whitelist: list[str] | None) -> dict[str, Any]:
"min",
"max",
"map",
# Not in RestrictedPython's list, but they don't give student code anything that plain Python
# syntax doesn't. Courses commonly use them, and without them, correct solutions fail with a
# NameError.
"all",
"any",
"ascii",
"bin",
"bytearray",
"classmethod",
"Ellipsis",
"filter",
"format",
"frozenset",
"iter",
"next",
"NotImplemented",
"object",
"property",
"reversed",
"staticmethod",
"sum",
"super",
"type",
)

_safe_exceptions = (
Expand Down
Empty file.
34 changes: 34 additions & 0 deletions tests/scenario_root/test_default_builtins/expected_outcome.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
{
"expected_data_object": {
"score": 1.0,
"tests": [
{
"max_points": 1,
"message": "",
"name": "common builtins",
"outcome": "passed",
"points": 1.0,
"points_frac": 1.0,
"test_id": "test_default_builtins.py::test_common_builtins[student_code]"
},
{
"max_points": 1,
"message": "",
"name": "classes",
"outcome": "passed",
"points": 1.0,
"points_frac": 1.0,
"test_id": "test_default_builtins.py::test_classes[student_code]"
},
{
"max_points": 1,
"message": "",
"name": "dangerous builtins stay blocked",
"outcome": "passed",
"points": 1.0,
"points_frac": 1.0,
"test_id": "test_default_builtins.py::test_open_blocked[student_code]"
}
]
}
}
34 changes: 34 additions & 0 deletions tests/scenario_root/test_default_builtins/scenario.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Commonly used builtins are available to student code without a builtin_whitelist."""

import pytest

from pytest_prairielearn_grader.fixture import StudentFixture


@pytest.mark.grading_data(name="common builtins", points=1)
def test_common_builtins(sandbox: StudentFixture) -> None:
assert sandbox.query_function("common_builtins", [3, 1, 2]) == [
6,
True,
True,
[2, 1, 3],
[3, 1],
3,
[1, 2, 3],
"3.14",
"0b101",
"'\\xe9'",
"list",
True,
]


@pytest.mark.grading_data(name="classes", points=1)
def test_classes(sandbox: StudentFixture) -> None:
assert sandbox.query_function("classes") == ["square!", 0, "circle"]


@pytest.mark.grading_data(name="dangerous builtins stay blocked", points=1)
def test_open_blocked(sandbox: StudentFixture) -> None:
response = sandbox.query_function_raw("call_open")
assert response["exception_name"] == "NameError"
46 changes: 46 additions & 0 deletions tests/scenario_root/test_default_builtins/student_code.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
class Shape:
def __init__(self, name):
self.name = name

@property
def label(self):
return f"{self.name}!"

@staticmethod
def sides():
return 0

@classmethod
def make(cls, name):
return cls(name)


class Square(Shape):
def __init__(self):
super().__init__("square")


def common_builtins(values):
return [
sum(values),
any(v > 2 for v in values),
all(v > 0 for v in values),
list(reversed(values)),
list(filter(lambda v: v % 2, values)),
next(iter(values)),
sorted(frozenset(values)),
format(3.14159, ".2f"),
bin(5),
ascii("é"),
type(values).__name__,
isinstance(object(), object),
]


def classes():
square = Square()
return [square.label, Square.sides(), Shape.make("circle").name]


def call_open():
return open("data.txt").read()
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ def triple(x):


def total(values):
return sum(values)
return vars(values)
Loading