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
6 changes: 3 additions & 3 deletions charts/grader-service-all-in-one/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,18 +15,18 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.8.1
version: 0.8.2

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.8.1"
appVersion: "0.8.2"

dependencies:
- name: jupyterhub
version: 4.2.0
repository: https://jupyterhub.github.io/helm-chart
- name: grader-service
version: 0.8.1
version: 0.8.2
repository: https://tu-wien-datalab.github.io/grader-service/
4 changes: 2 additions & 2 deletions charts/grader-service/Chart.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ type: application
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
# Versions are expected to follow Semantic Versioning (https://semver.org/)
version: 0.8.1
version: 0.8.2

# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application. Versions are not expected to
# follow Semantic Versioning. They should reflect the version the application is using.
# It is recommended to use it with quotes.
appVersion: "0.8.1"
appVersion: "0.8.2"
2 changes: 1 addition & 1 deletion charts/grader-service/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
github_url = "https://github.com/TU-Wien-dataLAB/grader-service"

[tool.tbump.version]
current = "0.8.1"
current = "0.8.2"

# Example of a semver regexp.
# Make sure this matches current_version before
Expand Down
2 changes: 1 addition & 1 deletion docs/source/_static/openapi/grader_api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ info:
description: The REST API for the grader service
license:
name: BSD-3-Clause
version: '0.8.1'
version: '0.8.2'
servers:
- url: /api
description: relative path to mock
Expand Down
7 changes: 3 additions & 4 deletions docs/source/configuration/auth.md
Original file line number Diff line number Diff line change
Expand Up @@ -165,10 +165,9 @@ c.GraderService.authenticator_class = DummyAuthenticator
c.Authenticator.allowed_users = {'admin', 'instructor', 'student', 'tutor'}
c.Authenticator.admin_users = {'admin'}
# default roles
c.GraderService.load_roles = {"lect1": {"members": ["admin", "instructor"], "role": "instructor"},
"lect1": {"members": ["tutor"], "role": "tutor"},
"lect1": {"members": ["student"], "role": "student"},
}
c.GraderService.load_roles = {"lect1": [{"members": ["admin", "instructor"], "role": "instructor"},
{"members": ["tutor"], "role": "tutor"},
{"members": ["student"], "role": "student"}]}

# JupyterHub client config
c.GraderService.oauth_clients = [{
Expand Down
2 changes: 1 addition & 1 deletion docs/source/installation/kubernetes.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
- [Install Helm](https://helm.sh/docs/intro/install/)
- [Add the Helm repository](https://helm.sh/docs/intro/quickstart/#add-a-helm-repository)
```bash
helm repo add grader-service ghcr.io/tu-wien-datalab/grader-service
helm repo add grader-service https://tu-wien-datalab.github.io/grader-service/
```
- [RabbitMQ operator](https://www.rabbitmq.com/kubernetes/operator/install-operator#installation) installed

Expand Down
8 changes: 5 additions & 3 deletions examples/dev_environment/grader_service_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@
c.Authenticator.allowed_users = {"admin", "instructor", "student", "tutor"}
c.Authenticator.admin_users = {"admin"}
c.GraderService.load_roles = {
"lect1": {"members": ["admin", "instructor"], "role": "instructor"},
"lect1": {"members": ["tutor"], "role": "tutor"},
"lect1": {"members": ["student"], "role": "student"},
"lecture1": [
{"members": ["student"], "role": "student"},
{"members": ["tutor"], "role": "tutor"},
{"members": ["instructor", "admin"], "role": "instructor"},
]
}
8 changes: 5 additions & 3 deletions examples/dev_environment/grader_service_config_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,7 +111,9 @@ def post_auth_hook(authenticator: Authenticator, handler: BaseHandler, authentic
c.Authenticator.admin_users = {"admin"}

c.GraderService.load_roles = {
"lect1:instructor": ["admin", "instructor"],
"lect1:student": ["student"],
"lect1:tutor": ["tutor"],
"lecture1": [
{"members": ["student"], "role": "student"},
{"members": ["tutor"], "role": "tutor"},
{"members": ["instructor", "admin"], "role": "instructor"},
]
}
2 changes: 1 addition & 1 deletion grader_service/_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
# LICENSE file in the root directory of this source tree.

# version_info updated by running `tbump`
__version__ = "0.8.1"
__version__ = "0.8.2"
7 changes: 5 additions & 2 deletions grader_service/autograding/celery/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,10 @@ def generate_feedback_task(self: GraderTask, lecture_id: int, assignment_id: int

executor = GenerateFeedbackExecutor(grader_service_dir, submission, config=self.celery.config)
executor.start()
self.log.info(f"Successfully generated feedback for submission {submission.id}!")
if submission.feedback_status == "generated":
self.log.info("Successfully generated feedback for submission %s!", submission.id)
else:
self.log.error("Failed to generate feedback for submission %s!", submission.id)


@app.task(bind=True, base=GraderTask)
Expand All @@ -94,7 +97,7 @@ async def lti_sync_task(
:param lecture: lecture object
:param assignment: assignment object
:param submissions: submissions to be synced
:param feedback_sync(optional): if True, the given submission is part of a fully automated grading assignment
:param feedback_sync(optional): if True, the sync task was started by a feedback generation
"""
lti_plugin = LTISyncGrades.instance()
# check if the lti plugin is enabled
Expand Down
12 changes: 6 additions & 6 deletions grader_service/autograding/local_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,22 +87,22 @@ def _run(self):
os.makedirs(self.output_path)
self._write_gradebook(self.submission.properties.properties)

autograder = GenerateFeedback(
feedback_generator = GenerateFeedback(
self.input_path,
self.output_path,
"*.ipynb",
assignment_settings=self.assignment.settings,
)
autograder.force = True
feedback_generator.force = True

log_stream = io.StringIO()
log_handler = logging.StreamHandler(log_stream)
autograder.log.addHandler(log_handler)
feedback_generator.log.addHandler(log_handler)

autograder.start()
feedback_generator.start()

self.grading_logs = log_stream.getvalue()
autograder.log.removeHandler(log_handler)
feedback_generator.log.removeHandler(log_handler)

def _push_results(self):
os.unlink(os.path.join(self.output_path, "gradebook.json"))
Expand Down Expand Up @@ -151,7 +151,7 @@ def _set_properties(self):
pass

def _set_db_state(self, success=True):
""" "
"""
Sets the submission feedback status based on the success of the generation.
:param success: Whether feedback generation was succesfull or not.
:return: None
Expand Down
33 changes: 15 additions & 18 deletions grader_service/autograding/local_grader.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import shlex
import shutil
import subprocess
from dataclasses import dataclass
from datetime import datetime
from pathlib import Path
from subprocess import CalledProcessError
Expand All @@ -33,13 +32,6 @@
from grader_service.orm.submission_properties import SubmissionProperties


@dataclass
class AutogradingStatus:
status: str
started_at: datetime
finished_at: datetime


def default_timeout_func(lecture: Lecture) -> int:
return 360

Expand Down Expand Up @@ -102,8 +94,9 @@ def start(self):
It re-raises all exceptions that happen while running.
"""
self.log.info(
f"Starting autograding job for submission "
f"{self.submission.id} in {self.__class__.__name__}"
"Starting autograding job for submission %s in %s",
self.submission.id,
self.__class__.__name__,
)
try:
self._pull_submission()
Expand All @@ -113,19 +106,23 @@ def start(self):
self._set_properties()
self._push_results()
self._set_db_state()
ts = round((self.autograding_finished - self.autograding_start).total_seconds())
self.log.info(
f"Successfully completed autograding job for submission "
f"{self.submission.id} in {self.__class__.__name__};"
+ f" took {ts // 60}min {ts % 60}s"
)
except Exception:
self.log.error(
f"Failed autograding job for submission "
f"{self.submission.id} in {self.__class__.__name__}",
"Failed autograding job for submission %s in %s",
self.submission.id,
self.__class__.__name__,
exc_info=True,
)
self._set_db_state(success=False)
else:
ts = round((self.autograding_finished - self.autograding_start).total_seconds())
self.log.info(
"Successfully completed autograding job for submission %s in %s; took %s min %s s",
self.submission.id,
self.__class__.__name__,
ts // 60,
ts % 60,
)
finally:
self._cleanup()

Expand Down
13 changes: 13 additions & 0 deletions grader_service/convert/converters/generate_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
from grader_service.convert import utils
from grader_service.convert.converters.base import BaseConverter
from grader_service.convert.converters.baseapp import ConverterApp
from grader_service.convert.gradebook.gradebook import MissingEntry
from grader_service.convert.preprocessors import GetGrades


Expand Down Expand Up @@ -54,6 +55,18 @@ def __init__(
self.update_config(c)
self.force = True # always overwrite generated assignments

def convert_single_notebook(self, notebook_filename: str) -> None:
"""Generate feedback for a single notebook.

We ignore any notebooks for which there are no gradebook entries
(e.g. additional notebooks created by the student), because feedback
generation would fail for them anyway.
"""
try:
super().convert_single_notebook(notebook_filename)
except MissingEntry:
self.log.info("Skipping notebook %s", notebook_filename)


class GenerateFeedbackApp(ConverterApp):
version = ConverterApp.__version__
Expand Down
10 changes: 6 additions & 4 deletions grader_service/convert/gradebook/gradebook.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,25 +114,27 @@ def add_notebook(self, name: str, **kwargs: dict) -> Notebook:
kwargs = {k: v for k, v in kwargs.items() if k in set(Notebook.empty_dict().keys())}
kwargs["name"] = name
self.model.notebooks[name] = Notebook.from_dict(kwargs)
return self.model.notebooks[name]

def find_notebook(self, name: str) -> Notebook:
"""
Find a particular notebook.
:param name: the name of the notebook
:return: notebook : :class:`~Notebook`
:return: notebook : :class:`~Notebook`
:raises: MissingEntry if `name` is not a key in the self.model.notebooks
"""
try:
return self.model.notebooks[name]
except KeyError:
raise MissingEntry()
raise MissingEntry(name) from None

@write_access
def update_or_create_notebook(self, name: str, **kwargs):
def update_or_create_notebook(self, name: str, **kwargs) -> Notebook:
"""
Update an existing notebook, or create it if it doesn't exist.
:param name: the name of the notebook
:param kwargs: additional keyword arguments for the :class:`~Notebook` object
:return: notebook : :class:`~nNotebook`
:return: notebook : :class:`~Notebook`
"""
raise NotImplementedError()

Expand Down
11 changes: 9 additions & 2 deletions grader_service/convert/preprocessors/getgrades.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from traitlets import List

from grader_service.convert import utils
from grader_service.convert.gradebook.gradebook import Gradebook
from grader_service.convert.gradebook.gradebook import Gradebook, MissingEntry
from grader_service.convert.preprocessors.base import NbGraderPreprocessor


Expand Down Expand Up @@ -38,7 +38,14 @@ def preprocess(
with self.gradebook:
# process the cells
nb, resources = super(GetGrades, self).preprocess(nb, resources)
notebook = self.gradebook.find_notebook(self.notebook_id)

try:
notebook = self.gradebook.find_notebook(self.notebook_id)
except MissingEntry:
self.log.info(
"Did not find a gradebook entry for the notebook %s", self.notebook_id
)
raise

resources["nbgrader"]["score"] = notebook.score
resources["nbgrader"]["max_score"] = notebook.max_score
Expand Down
Loading
Loading