Skip to content

Commit

Permalink
Merge pull request #109 from DigiKlausur/wus
Browse files Browse the repository at this point in the history
Add WuS Notebook and remove jupyter_server_terminals
  • Loading branch information
tmetzl authored Nov 12, 2024
2 parents ec2eb1c + 52d9108 commit 2628791
Show file tree
Hide file tree
Showing 6 changed files with 230 additions and 34 deletions.
30 changes: 30 additions & 0 deletions .github/workflows/build_and_push_all_images.yml
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,36 @@ jobs:
e2xgrader_branch: ${{ inputs.e2xgrader_branch }}
secrets: inherit

wus_notebook:
needs: [datascience_notebook]
uses: ./.github/workflows/build_image.yml
with:
force_build: ${{ inputs.force_build || needs.datascience_notebook.outputs.did_build_image == 'true' }}
registry: ${{ inputs.registry }}
base_image_name: ${{ inputs.registry }}/digiklausur/docker-stacks/datascience-notebook
base_image_tag: ${{ inputs.tag }}
image_path: images/wus-notebook
image_name: wus-notebook
image_tag: ${{ inputs.tag }}
push: ${{ inputs.push }}
secrets: inherit

e2x_wus_notebook:
needs: [wus_notebook]
uses: ./.github/workflows/build_e2xgrader_images.yml
with:
force_build: ${{ inputs.force_build || needs.wus_notebook.outputs.did_build_image == 'true' }}
registry: ${{ inputs.registry }}
image_name: wus-notebook
image_tag: ${{ inputs.tag }}
base_image_name: ${{ inputs.registry }}/digiklausur/docker-stacks/wus-notebook
base_image_tag: ${{ inputs.tag }}
push: ${{ inputs.push }}
e2xgrader_installation_source: ${{ inputs.e2xgrader_installation_source }}
e2xgrader_version: ${{ inputs.e2xgrader_version }}
e2xgrader_branch: ${{ inputs.e2xgrader_branch }}
secrets: inherit

nlp_notebook:
needs: [ml_notebook]
uses: ./.github/workflows/build_image.yml
Expand Down
56 changes: 23 additions & 33 deletions images/minimal-notebook/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,18 @@ ENV TZ=Europe/Berlin

RUN echo "en_US.UTF-8 UTF-8" > /etc/locale.gen && \
locale-gen
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
ENV LANGUAGE en_US.UTF-8

RUN ln -snf /usr/share/zoneinfo/"$TZ" /etc/localtime && echo "$TZ" > /etc/timezone

# Create default config for ipython kernel config
RUN mkdir /etc/ipython/ && \
chown root:"$NB_GID" /etc/ipython/ &&\
chmod g+rwX /etc/ipython/ &&\
chmod +6000 /etc/ipython/

# Remove default work dir
RUN rm -rf "$HOME"/work

RUN apt-get update -y \
ENV LC_ALL=en_US.UTF-8
ENV LANG=en_US.UTF-8
ENV LANGUAGE=en_US.UTF-8

# Set timezone and create default config for ipython kernel config
RUN ln -snf /usr/share/zoneinfo/"$TZ" /etc/localtime && echo "$TZ" > /etc/timezone \
&& mkdir /etc/ipython/ \
&& chown root:"$NB_GID" /etc/ipython/ \
&& chmod g+rwX /etc/ipython/ \
&& chmod +6000 /etc/ipython/ \
&& rm -rf "$HOME"/work \
&& apt-get update -y \
&& apt-get install --yes \
ncdu \
vim \
Expand All @@ -40,29 +36,23 @@ RUN apt-get update -y \

USER $NB_USER

# Install git and graphviz
# Install requirements and enable extenstions and Jupyter contrib extensions
COPY requirements.txt /tmp/requirements.txt

# Install git and graphviz and upgrade pip
RUN mamba install -y gh --channel conda-forge \
&& mamba install -y graphviz --no-update-deps \
&& mamba clean -afy \
&& npm cache clean --force \
&& find /opt/conda/ -follow -type f -name '*.js.map' -delete

RUN pip install --no-cache-dir --upgrade pip \
&& find /opt/conda/ -follow -type f -name '*.js.map' -delete

# Install requirements
COPY requirements.txt /tmp/requirements.txt
RUN pip install --no-cache-dir -r /tmp/requirements.txt \
&& find /opt/conda/ -follow -type f -name '*.js.map' -delete

# Enable extenstions and Jupyter contrib extensions
RUN cd /tmp \
&& pip install --no-cache-dir --upgrade pip \
&& pip install --no-cache-dir -r /tmp/requirements.txt \
&& pip uninstall "jupyter-server-terminals" -y \
&& find /opt/conda/ -follow -type f -name '*.js.map' -delete \
&& cd /tmp \
&& git clone --depth 1 -b master https://github.com/ipython-contrib/jupyter_contrib_nbextensions.git \
&& cd /tmp/jupyter_contrib_nbextensions/src/jupyter_contrib_nbextensions/nbextensions \
&& jupyter nbextension install --sys-prefix execute_time \
&& jupyter nbextension enable --sys-prefix execute_time/ExecuteTime \
&& jupyter nbextension list \
&& rm -rf /tmp/jupyter_contrib_nbextensions

# Jupyter extension list
RUN jupyter nbextension list
&& rm -rf /tmp/jupyter_contrib_nbextensions \
&& jupyter nbextension list
3 changes: 2 additions & 1 deletion images/minimal-notebook/requirements.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
nbresuse
nbresuse
jupyter-server<2.0.0
22 changes: 22 additions & 0 deletions images/wus-notebook/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
ARG IMAGE_SOURCE=ghcr.io/digiklausur/docker-stacks/minimal-notebook:latest
FROM $IMAGE_SOURCE

LABEL maintainer="e2x project H-BRS <e2x@inf.h-brs.de>"
LABEL description="e2x notebook for statistics course"
LABEL org.opencontainers.image.description="e2x notebook for statistics course"
LABEL org.opencontainers.image.authors="e2x project H-BRS <e2x@inf.h-brs.de>"

USER root
# Copy nbgrader wus config
COPY configs/nbgrader_config.py /opt/conda/etc/jupyter/nbgrader_config.py
RUN chown root:"$NB_GID" /opt/conda/etc/jupyter/nbgrader_config.py &&\
chmod g+rwX /opt/conda/etc/jupyter/nbgrader_config.py

USER $NB_USER

# Disable execute time extension and enable highlight word
RUN jupyter nbextension disable --sys-prefix execute_time/ExecuteTime \
&& pip install --no-cache-dir jupyter_highlight_selected_word \
&& jupyter nbextension install --py --sys-prefix jupyter_highlight_selected_word \
&& jupyter nbextension enable --sys-prefix highlight_selected_word/main \
&& jupyter nbextension list
54 changes: 54 additions & 0 deletions images/wus-notebook/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
# e2x Data Science Notebook

TThis Docker image is a customized version of the [e2x Data Science Notebook](../datascience-notebook) image, with certain extensions specific to the WuS course at H-BRS installed.

## Build Args

The following build arg is available:

* `IMAGE_SOURCE`: The base image to use for the build. Defaults to `ghcr.io/digiklausur/docker-stacks/datascience-notebook:latest`.

## Description

This image is designed to provide a comprehensive Jupyter Notebook environment for data science tasks, building on top of the e2x Minimal Notebook image. It includes:

* All features from the e2x Minimal Notebook image
* A collection of popular Python data science libraries, including NumPy, Pandas, SciPy, Matplotlib, Scikit-learn, and more

## Versions

This images comes as a basic data science image or with `e2xgrader` installed and a specific mode activated.
For more information look at the [E2xGrader Notebook](../e2xgrader-notebook) image and the [e2xgrader](https://github.com/Digiklausur/e2xgrader) package.

* `wus-notebook`
+ Base WuS science image
* `wus-notebook-teacher`
+ Base data science image with `e2xgrader` teacher mode activated (includes grading tools)
* `wus-notebook-student`
+ Base data science image with `e2xgrader` student mode activated (includes extensions for students)
* `wus-notebook-exam`
+ Base data science image with `e2xgrader` student_exam mode activated (provides a restricted notebook for students in an exam)

## Usage

### Pull and Run

To pull and run the image use:

`docker run -p 8888:8888 ghcr.io/digiklausur/docker-stacks/wus-notebook:latest`

Available tags are `latest` and `dev`. Available registries are `quay.io` and `ghcr.io`.

### Build and Run

To build the image from the standard source, run:

`docker build -t wus-notebook:dev .`

To build the image from a custom source, run:

`docker build -t wus-notebook:dev . --build-arg="IMAGE_SOURCE=<your_base_image>:<your_tag>"`

To run the image, use:

`docker run -p 8888:8888 wus-notebook:dev`
99 changes: 99 additions & 0 deletions images/wus-notebook/configs/nbgrader_config.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
from nbgrader.preprocessors import GetGrades
from nbformat import NotebookNode
from nbconvert.exporters.exporter import ResourcesDict
from nbgrader.api import MissingEntry
from nbconvert.preprocessors import CSSHTMLHeaderPreprocessor
from e2xgrader.preprocessors import FilterTests

from e2xgrader.graders import MultipleChoiceGrader, CodeGrader, SingleChoiceGrader
from e2xgrader.utils.extra_cells import get_choices, get_instructor_choices
from traitlets import Unicode

class GetGradesWithUngradedComment(GetGrades):
"""
If a cell has not been graded, this processor adds a comment
saying "Unbewertet" to the cell
"""

ungraded_comment = Unicode(
"Unbewertet",
help="Comment to add to ungraded cells"
).tag(config=True)

def _get_comment(self, cell: NotebookNode, resources: ResourcesDict) -> None:
"""Graders can optionally add comments to the student's solutions, so
add the comment information into the database if it doesn't
already exist. It should NOT overwrite existing comments that
might have been added by a grader already.
"""
comment = self.gradebook.find_comment(
cell.metadata['nbgrader']['grade_id'],
self.notebook_id,
self.assignment_id,
self.student_id)

needs_manual_grade = False
try:
grade = self.gradebook.find_grade(
cell.metadata['nbgrader']['grade_id'],
self.notebook_id,
self.assignment_id,
self.student_id)
needs_manual_grade = grade.needs_manual_grade
except MissingEntry:
pass

comment = comment.comment

if comment is None:
comment = ""
else:
comment += "\n"

# save it in the notebook
if needs_manual_grade:
cell.metadata.nbgrader['comment'] = comment + self.ungraded_comment
else:
cell.metadata.nbgrader['comment'] = comment


class WuSMultipleChoiceGrader(MultipleChoiceGrader):

def determine_grade(self, cell, log=None):
'''
Grader for multiple choice questions
Only give full points if student did select all correct
answers and no incorrect answers
'''
max_points = float(cell.metadata['nbgrader']['points'])
student_choices = get_choices(cell)
instructor_choices = get_instructor_choices(cell)

# Return 0 points if the student did not select all correct answers
for choice in instructor_choices:
if choice not in student_choices:
return 0, max_points

# Return 0 points if the student did select an incorrect answer
for choice in student_choices:
if choice not in instructor_choices:
return 0, max_points

return max_points, max_points

c = get_config() # noqa: F821

c.GenerateFeedback.preprocessors = [
GetGradesWithUngradedComment,
FilterTests,
CSSHTMLHeaderPreprocessor
]

c.SaveAutoGrades.graders = {
'multiplechoice': WuSMultipleChoiceGrader(),
'code': CodeGrader(),
'singlechoice': SingleChoiceGrader()
}

0 comments on commit 2628791

Please sign in to comment.