Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Compatibility with nbconvert v6 #1421

Closed
wants to merge 12 commits into from
Closed
5 changes: 3 additions & 2 deletions nbgrader/apps/api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1066,9 +1066,10 @@ def generate_feedback(self, assignment_id, student_id=None, force=True):
"""
# Because we may be using HTMLExporter.template_file in other
# parts of the the UI, we need to make sure that the template
# is explicitply 'feedback.tpl` here:
# is explicitly 'feedback.html.j2` here:
c = Config()
c.HTMLExporter.template_file = 'feedback.tpl'
c.HTMLExporter.template_name = 'feedback'
c.HTMLExporter.template_file = 'feedback.html.j2'
if student_id is not None:
with temp_attrs(self.coursedir,
assignment_id=assignment_id,
Expand Down
7 changes: 7 additions & 0 deletions nbgrader/apps/nbgraderapp.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import sys
import os

import asyncio
from textwrap import dedent

from traitlets import default
Expand Down Expand Up @@ -53,6 +54,12 @@
})


# See https://bugs.python.org/issue37373 :(
# Workaround from https://github.com/jupyter/nbconvert/issues/1372
if sys.version_info[0] == 3 and sys.version_info[1] >= 8 and sys.platform.startswith('win'):
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After looking at how the extensions are organized, I'm not sure this import happens for the server extensions. Try moving this to a step in nbgrader.apps.baseapp.NbGrader.initialize(), then it might be enough.

asyncio.set_event_loop_policy(asyncio.WindowsSelectorEventLoopPolicy())


class NbGraderApp(NbGrader):

name = u'nbgrader'
Expand Down
3 changes: 2 additions & 1 deletion nbgrader/converters/autograde.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
from .base import BaseConverter, NbGraderException
from ..preprocessors import (
AssignLatePenalties, ClearOutput, DeduplicateIds, OverwriteCells, SaveAutoGrades,
Execute, LimitOutput, OverwriteKernelspec, CheckCellMetadata)
Execute, LimitOutput, OverwriteKernelspec, CheckCellMetadata, RemoveExecutionInfo)
from ..api import Gradebook, MissingEntry
from .. import utils

Expand Down Expand Up @@ -59,6 +59,7 @@ def _output_directory(self) -> str:
]).tag(config=True)
autograde_preprocessors = List([
Execute,
RemoveExecutionInfo,
LimitOutput,
SaveAutoGrades,
AssignLatePenalties,
Expand Down
9 changes: 4 additions & 5 deletions nbgrader/converters/generate_feedback.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,9 +55,8 @@ def _load_config(self, cfg, **kwargs):
def __init__(self, coursedir=None, **kwargs):
super(GenerateFeedback, self).__init__(coursedir=coursedir, **kwargs)
c = Config()
if 'template_file' not in self.config.HTMLExporter:
c.HTMLExporter.template_file = 'feedback.tpl'
if 'template_path' not in self.config.HTMLExporter:
template_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'server_extensions', 'formgrader', 'templates'))
c.HTMLExporter.template_path = ['.', template_path]
c.HTMLExporter.template_name = 'feedback'
c.HTMLExporter.template_file = 'feedback.html.j2'
template_path = os.path.abspath(os.path.join(os.path.dirname(__file__), '..', 'server_extensions', 'formgrader', 'templates'))
c.HTMLExporter.extra_template_basedirs.append(template_path)
self.update_config(c)
2 changes: 2 additions & 0 deletions nbgrader/preprocessors/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
from .clearhiddentests import ClearHiddenTests
from .clearmarkingscheme import ClearMarkScheme
from .overwritekernelspec import OverwriteKernelspec
from .removeexecutioninfo import RemoveExecutionInfo

__all__ = [
"AssignLatePenalties",
Expand All @@ -35,4 +36,5 @@
"ClearHiddenTests",
"ClearMarkScheme",
"OverwriteKernelspec",
"RemoveExecutionInfo"
]
94 changes: 46 additions & 48 deletions nbgrader/preprocessors/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
from . import NbGraderPreprocessor
from nbconvert.exporters.exporter import ResourcesDict
from nbformat.notebooknode import NotebookNode
from typing import Any, Optional, Tuple
from typing import Any, Optional, Tuple, Dict


class UnresponsiveKernelError(Exception):
Expand Down Expand Up @@ -56,50 +56,48 @@ def preprocess(self,

return output

def preprocess_cell(self, cell, resources, cell_index, store_history=True):
"""
Need to override preprocess_cell to check reply for errors
"""
# Copied from nbconvert ExecutePreprocessor
if cell.cell_type != 'code' or not cell.source.strip():
return cell, resources

reply, outputs = self.run_cell(cell, cell_index, store_history)
# Backwards compatibility for processes that wrap run_cell
cell.outputs = outputs

cell_allows_errors = (self.allow_errors or "raises-exception"
in cell.metadata.get("tags", []))

if self.force_raise_errors or not cell_allows_errors:
if (reply is not None) and reply['content']['status'] == 'error':
raise CellExecutionError.from_cell_and_msg(cell, reply['content'])

# Ensure errors are recorded to prevent false positives when autograding
if (reply is None) or reply['content']['status'] == 'error':
error_recorded = False
for output in cell.outputs:
if output.output_type == 'error':
error_recorded = True
if not error_recorded:
error_output = NotebookNode(output_type='error')
if reply is None:
# Occurs when
# IPython.core.interactiveshell.InteractiveShell.showtraceback
# = None
error_output.ename = "CellTimeoutError"
error_output.evalue = ""
error_output.traceback = ["ERROR: No reply from kernel"]
else:
# Occurs when
# IPython.core.interactiveshell.InteractiveShell.showtraceback
# = lambda *args, **kwargs : None
error_output.ename = reply['content']['ename']
error_output.evalue = reply['content']['evalue']
error_output.traceback = reply['content']['traceback']
if error_output.traceback == []:
error_output.traceback = ["ERROR: An error occurred while"
" showtraceback was disabled"]
cell.outputs.append(error_output)

return cell, resources
def _check_raise_for_error(
self,
cell: NotebookNode,
exec_reply: Optional[Dict]) -> None:

exec_reply_content = None
if exec_reply is not None:
exec_reply_content = exec_reply['content']
if exec_reply_content['status'] != 'error':
return None

cell_allows_errors = (not self.force_raise_errors) and (
self.allow_errors
or exec_reply_content.get('ename') in self.allow_error_names
or "raises-exception" in cell.metadata.get("tags", []))

if not cell_allows_errors:
raise CellExecutionError.from_cell_and_msg(cell, exec_reply_content)

# Ensure errors are recorded to prevent false positives when autograding
if exec_reply is None or exec_reply_content['status'] == 'error':
error_recorded = False
for output in cell.outputs:
if output.output_type == 'error':
error_recorded = True
break

if not error_recorded:
error_output = NotebookNode(output_type='error')
if exec_reply is None:
# Occurs when
# IPython.core.interactiveshell.InteractiveShell.showtraceback = None
error_output.ename = "CellTimeoutError"
error_output.evalue = ""
error_output.traceback = ["ERROR: No reply from kernel"]
else:
# Occurs when
# IPython.core.interactiveshell.InteractiveShell.showtraceback = lambda *args, **kwargs: None
error_output.ename = exec_reply_content['ename']
error_output.evalue = exec_reply_content['evalue']
error_output.traceback = exec_reply_content['traceback']
if error_output.traceback == []:
error_output.traceback = ["ERROR: An error occurred while"
" showtraceback was disabled"]
cell.outputs.append(error_output)
3 changes: 3 additions & 0 deletions nbgrader/preprocessors/getgrades.py
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,9 @@ def preprocess(self,
resources['nbgrader']['max_score'] = notebook.max_score
resources['nbgrader']['late_penalty'] = late_penalty

# Set title
nb['metadata']['title'] = resources['nbgrader']['notebook']

return nb, resources

def _get_comment(self, cell: NotebookNode, resources: ResourcesDict) -> None:
Expand Down
19 changes: 19 additions & 0 deletions nbgrader/preprocessors/removeexecutioninfo.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
from . import NbGraderPreprocessor

from traitlets import Integer
from nbformat.notebooknode import NotebookNode
from nbconvert.exporters.exporter import ResourcesDict
from typing import Tuple


class RemoveExecutionInfo(NbGraderPreprocessor):
"""Preprocessor for removing execution info."""

def preprocess_cell(self,
cell: NotebookNode,
resources: ResourcesDict,
cell_index: int
) -> Tuple[NotebookNode, ResourcesDict]:
if 'execution' in cell['metadata']:
del cell['metadata']['execution']
return cell, resources
6 changes: 3 additions & 3 deletions nbgrader/server_extensions/formgrader/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,19 +62,19 @@ def render(self, name, **ns):
def write_error(self, status_code, **kwargs):
if status_code == 500:
html = self.render(
'base_500.tpl',
'pages/base_500.html.j2',
base_url=self.base_url,
error_code=500)

elif status_code == 502:
html = self.render(
'base_500.tpl',
'pages/base_500.html.j2',
base_url=self.base_url,
error_code=502)

elif status_code == 403:
html = self.render(
'base_403.tpl',
'pages/base_403.html.j2',
base_url=self.base_url,
error_code=403)

Expand Down
10 changes: 7 additions & 3 deletions nbgrader/server_extensions/formgrader/formgrader.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,17 @@ def _classes_default(self):

def build_extra_config(self):
extra_config = super(FormgradeExtension, self).build_extra_config()
extra_config.HTMLExporter.template_file = 'formgrade'
extra_config.HTMLExporter.template_path = [handlers.template_path]
extra_config.HTMLExporter.template_name = 'formgrade'
extra_config.HTMLExporter.template_file = 'formgrade.html.j2'
extra_config.HTMLExporter.extra_template_basedirs.append(handlers.template_path)
return extra_config

def init_tornado_settings(self, webapp):
# Init jinja environment
jinja_env = Environment(loader=FileSystemLoader([handlers.template_path]))
jinja_env = Environment(loader=FileSystemLoader([
os.path.join(handlers.template_path, 'formgrade'),
os.path.join(handlers.template_path, 'pages')
]))

course_dir = self.coursedir.root
notebook_dir = self.parent.notebook_dir
Expand Down
18 changes: 9 additions & 9 deletions nbgrader/server_extensions/formgrader/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ class ManageAssignmentsHandler(BaseHandler):
@check_notebook_dir
def get(self):
html = self.render(
"manage_assignments.tpl",
"manage_assignments.html.j2",
url_prefix=self.url_prefix,
base_url=self.base_url,
windows=(sys.prefix == 'win32'),
Expand All @@ -30,7 +30,7 @@ class ManageSubmissionsHandler(BaseHandler):
@check_notebook_dir
def get(self, assignment_id):
html = self.render(
"manage_submissions.tpl",
"manage_submissions.html.j2",
course_dir=self.coursedir.root,
assignment_id=assignment_id,
base_url=self.base_url)
Expand All @@ -43,7 +43,7 @@ class GradebookAssignmentsHandler(BaseHandler):
@check_notebook_dir
def get(self):
html = self.render(
"gradebook_assignments.tpl",
"gradebook_assignments.html.j2",
base_url=self.base_url)
self.write(html)

Expand All @@ -54,7 +54,7 @@ class GradebookNotebooksHandler(BaseHandler):
@check_notebook_dir
def get(self, assignment_id):
html = self.render(
"gradebook_notebooks.tpl",
"gradebook_notebooks.html.j2",
assignment_id=assignment_id,
base_url=self.base_url)
self.write(html)
Expand All @@ -66,7 +66,7 @@ class GradebookNotebookSubmissionsHandler(BaseHandler):
@check_notebook_dir
def get(self, assignment_id, notebook_id):
html = self.render(
"gradebook_notebook_submissions.tpl",
"gradebook_notebook_submissions.html.j2",
assignment_id=assignment_id,
notebook_id=notebook_id,
base_url=self.base_url
Expand Down Expand Up @@ -116,7 +116,7 @@ def get(self, submission_id):

if not os.path.exists(filename):
resources['filename'] = filename
html = self.render('formgrade_404.tpl', resources=resources)
html = self.render('formgrade_404.html.j2', resources=resources)
self.clear()
self.set_status(404)
self.write(html)
Expand Down Expand Up @@ -235,7 +235,7 @@ class ManageStudentsHandler(BaseHandler):
@check_notebook_dir
def get(self):
html = self.render(
"manage_students.tpl",
"manage_students.html.j2",
base_url=self.base_url)
self.write(html)

Expand All @@ -246,7 +246,7 @@ class ManageStudentsAssignmentsHandler(BaseHandler):
@check_notebook_dir
def get(self, student_id):
html = self.render(
"manage_students_assignments.tpl",
"manage_students_assignments.html.j2",
student_id=student_id,
base_url=self.base_url)

Expand All @@ -259,7 +259,7 @@ class ManageStudentNotebookSubmissionsHandler(BaseHandler):
@check_notebook_dir
def get(self, student_id, assignment_id):
html = self.render(
"manage_students_notebook_submissions.tpl",
"manage_students_notebook_submissions.html.j2",
assignment_id=assignment_id,
student_id=student_id,
base_url=self.base_url
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"base_template": "classic",
"mimetypes": {
"text/html": true
},
"preprocessors": {}
}
Loading