Skip to content

Commit

Permalink
WIP: updated labextension to use grader 0.6 api endpoint routes
Browse files Browse the repository at this point in the history
  • Loading branch information
florian-jaeger committed Oct 24, 2024
1 parent aa25820 commit 0f596d4
Show file tree
Hide file tree
Showing 36 changed files with 1,117 additions and 1,283 deletions.
74 changes: 35 additions & 39 deletions grader_labextension/handlers/assignment.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,22 @@
import json
import shutil

from tornado.httpclient import HTTPClientError
from tornado.web import HTTPError
from tornado.web import HTTPError, authenticated

from grader_labextension.registry import register_handler
from grader_labextension.handlers.base_handler import ExtensionBaseHandler, cache
from grader_labextension.services.request import RequestService
from grader_labextension.services.request import RequestService, RequestServiceError
import tornado
import os


@register_handler(path=r"\/lectures\/(?P<lecture_id>\d*)\/assignments\/?")
@register_handler(path=r"api\/lectures\/(?P<lecture_id>\d*)\/assignments\/?")
class AssignmentBaseHandler(ExtensionBaseHandler):
"""
Tornado Handler class for http requests to /lectures/{lecture_id}/assignments.
"""

@authenticated
async def get(self, lecture_id: int):
"""Sends a GET request to the grader service and returns assignments of the lecture
Expand All @@ -32,19 +32,19 @@ async def get(self, lecture_id: int):
try:
response = await self.request_service.request(
method="GET",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments",
endpoint=f"{self.service_base_url}api/lectures/{lecture_id}/assignments",
header=self.grader_authentication_header,
response_callback=self.set_service_headers
)

lecture = await self.request_service.request(
"GET",
f"{self.service_base_url}/lectures/{lecture_id}",
f"{self.service_base_url}api/lectures/{lecture_id}",
header=self.grader_authentication_header,
)
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)

# Create directories for every assignment
try:
Expand Down Expand Up @@ -76,19 +76,19 @@ async def post(self, lecture_id: int):
try:
response = await self.request_service.request(
method="POST",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments",
endpoint=f"{self.service_base_url}api/lectures/{lecture_id}/assignments",
body=data,
header=self.grader_authentication_header,
)

lecture = await self.request_service.request(
"GET",
f"{self.service_base_url}/lectures/{lecture_id}",
f"{self.service_base_url}api/lectures/{lecture_id}",
header=self.grader_authentication_header,
)
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)
# if we did not get an error when creating the assignment (i.e. the user is authorized etc.) then we can
# create the directory structure if it does not exist yet
os.makedirs(
Expand All @@ -107,7 +107,7 @@ async def post(self, lecture_id: int):


@register_handler(
path=r"\/lectures\/(?P<lecture_id>\d*)\/assignments\/(?P<assignment_id>\d*)\/?"
path=r"api\/lectures\/(?P<lecture_id>\d*)\/assignments\/(?P<assignment_id>\d*)\/?"
)
class AssignmentObjectHandler(ExtensionBaseHandler):
"""
Expand All @@ -127,15 +127,16 @@ async def put(self, lecture_id: int, assignment_id: int):
try:
response = await self.request_service.request(
method="PUT",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments/{assignment_id}",
endpoint=f"{self.service_base_url}api/lectures/{lecture_id}/assignments/{assignment_id}",
body=data,
header=self.grader_authentication_header,
)
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)
self.write(json.dumps(response))

@authenticated
async def get(self, lecture_id: int, assignment_id: int):
"""Sends a GET-request to the grader service to get a specific assignment
Expand All @@ -145,27 +146,21 @@ async def get(self, lecture_id: int, assignment_id: int):
:type assignment_id: int
"""

query_params = RequestService.get_query_string(
{
"instructor-version": self.get_argument("instructor-version", None),
}
)

try:
response = await self.request_service.request(
method="GET",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments/{assignment_id}{query_params}",
endpoint=f"{self.service_base_url}api/lectures/{lecture_id}/assignments/{assignment_id}",
header=self.grader_authentication_header,
response_callback=self.set_service_headers
)
lecture = await self.request_service.request(
"GET",
f"{self.service_base_url}/lectures/{lecture_id}",
f"{self.service_base_url}api/lectures/{lecture_id}",
header=self.grader_authentication_header,
)
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)

os.makedirs(
os.path.expanduser(f'{self.root_dir}/{lecture["code"]}/assignments/{response["id"]}'),
Expand All @@ -185,24 +180,25 @@ async def delete(self, lecture_id: int, assignment_id: int):
try:
await self.request_service.request(
method="DELETE",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments/{assignment_id}",
endpoint=f"{self.service_base_url}api/lectures/{lecture_id}/assignments/{assignment_id}",
header=self.grader_authentication_header,
decode_response=False
)
except HTTPClientError as e:
raise HTTPError(e.code, reason=e.response.reason)
except RequestServiceError as e:
raise HTTPError(e.code, reason=e.message)

self.write("OK")
self.write({"status": "OK"})


@register_handler(
path=r"\/lectures\/(?P<lecture_id>\d*)\/assignments\/(?P<assignment_id>\d*)\/properties\/?"
path=r"api\/lectures\/(?P<lecture_id>\d*)\/assignments\/(?P<assignment_id>\d*)\/properties\/?"
)
class AssignmentPropertiesHandler(ExtensionBaseHandler):
"""
Tornado Handler class for http requests to /lectures/{lecture_id}/assignments/{assignment_id}/properties.
"""

@authenticated
async def get(self, lecture_id: int, assignment_id: int):
"""Sends a GET-request to the grader service and returns the properties of an assignment
Expand All @@ -215,12 +211,12 @@ async def get(self, lecture_id: int, assignment_id: int):
try:
response = await self.request_service.request(
method="GET",
endpoint=f"{self.service_base_url}/lectures/{lecture_id}/assignments/{assignment_id}/properties",
endpoint=f"{self.service_base_url}api/lectures/{lecture_id}/assignments/{assignment_id}/properties",
header=self.grader_authentication_header,
response_callback=self.set_service_headers
)
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)
self.write(json.dumps(response))

43 changes: 12 additions & 31 deletions grader_labextension/handlers/base_handler.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,10 @@
from tornado.web import HTTPError

from grader_labextension.api.models.error_message import ErrorMessage
from grader_labextension.services.request import RequestService
from grader_labextension.services.request import RequestService, RequestServiceError
from jupyter_server.base.handlers import APIHandler
import os
from tornado.httpclient import HTTPClientError, HTTPResponse
from tornado.httpclient import HTTPResponse
from traitlets.config.configurable import SingletonConfigurable
from traitlets.traitlets import Unicode

Expand Down Expand Up @@ -44,7 +44,7 @@ class HandlerConfig(SingletonConfigurable):
grader_api_token = Unicode(os.environ.get("GRADER_API_TOKEN"),
help="The authorization token to access the grader service api").tag(config=True)
service_base_url = Unicode(
os.environ.get("GRADER_BASE_URL", "/services/grader"),
os.environ.get("GRADER_BASE_URL", "/"),
help="Base URL to use for each request to the grader service",
).tag(config=True)
lectures_base_path = Unicode(
Expand Down Expand Up @@ -95,42 +95,23 @@ async def get_lecture(self, lecture_id) -> dict:
try:
lecture = await self.request_service.request(
"GET",
f"{self.service_base_url}/lectures/{lecture_id}",
f"{self.service_base_url}api/lectures/{lecture_id}",
header=self.grader_authentication_header,
)
return lecture
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)

async def get_assignment(self, lecture_id, assignment_id):
try:
assignment = await self.request_service.request(
"GET",
f"{self.service_base_url}/lectures/{lecture_id}/assignments/{assignment_id}",
f"{self.service_base_url}api/lectures/{lecture_id}/assignments/{assignment_id}",
header=self.grader_authentication_header,
)
return assignment
except HTTPClientError as e:
self.log.error(e.response)
raise HTTPError(e.code, reason=e.response.reason)

def write_error(self, status_code, **kwargs):
"""APIHandler errors are JSON, not human pages"""
self.set_header("Content-Type", "application/json")
message = responses.get(status_code, "Unknown HTTP Error")
reply: dict = {
"message": message,
}
exc_info = kwargs.get("exc_info")
if exc_info:
e = exc_info[1]
if isinstance(e, HTTPError):
reply["message"] = e.log_message or message
reply["reason"] = e.reason
else:
reply["message"] = "Unhandled error"
reply["reason"] = None
reply["traceback"] = "".join(traceback.format_exception(*exc_info))
self.log.warning("wrote error: %r", reply["message"], exc_info=True)
self.finish(json.dumps(reply))
except RequestServiceError as e:
self.log.error(e)
raise HTTPError(e.code, reason=e.message)

Loading

0 comments on commit 0f596d4

Please sign in to comment.