diff --git a/examples/submission.ipynb b/examples/submission.ipynb new file mode 100644 index 0000000000000000000000000000000000000000..c9a7c1378de4d91a7a0560e760f0b886a638bbe5 --- /dev/null +++ b/examples/submission.ipynb @@ -0,0 +1,156 @@ +{ + "cells": [ + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "# Example notebooks for using the submission service" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "from rwth_nb.misc.submission import Submission\n", + "\n", + "from random import randint\n", + "from pprint import pprint\n", + " \n", + "sub = Submission('test_realm')" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('\\nCreate some random submissions...')\n", + "for i in range(5):\n", + " sub.submit({\n", + " 'random_integer': randint(1, 100)\n", + " })" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Admin actions\n", + "\n", + "For performing admin actions the user must own the role manager-{profile_slug} or submission-{realm}" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "print('\\nAll submissions:')\n", + "pprint(sub.get_all())\n", + " \n", + "print('\\nDelete all submissions')\n", + "sub.delete_all()\n", + " \n", + "print('\\nAll submissions:')\n", + "pprint(sub.get_all())" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## User actions" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "if sub.is_submitted():\n", + " print('\\nEvaluation already submitted')\n", + "else:\n", + " print('\\nNothing submitted yet!')\n", + " sub.submit({'test': [1, 2, 3]})\n", + " \n", + " if sub.is_submitted():\n", + " print('\\nNow it is submitted!:')\n", + " pprint(sub.get())\n", + " \n", + "print('\\nMy submissions:')\n", + "mysubs = sub.get()\n", + "print(mysubs)\n", + " \n", + "ident = mysubs[0]['id']\n", + "print(f'\\nMy submission ID: {ident}')\n", + " \n", + "print(f'\\nDeleting my submission with ID: {ident}')\n", + "sub.delete(ident=ident)\n", + " \n", + "print('\\nMy submissions:')\n", + "mysubs = sub.get()\n", + "print(mysubs)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "## Submitting whole notebooks\n", + "\n", + "The following cell submits the current notebook contents" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sub.submit_notebook()" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "The following cell fetches all submitted notebooks in the current realm by the current user and stores them in the current directory" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "sub.fetch_notebook(subdir='fetched_notebooks', prefix='a1')" + ] + } + ], + "metadata": { + "kernelspec": { + "display_name": "Python 3", + "language": "python", + "name": "python3" + }, + "language_info": { + "codemirror_mode": { + "name": "ipython", + "version": 3 + }, + "file_extension": ".py", + "mimetype": "text/x-python", + "name": "python", + "nbconvert_exporter": "python", + "pygments_lexer": "ipython3", + "version": "3.8.6" + } + }, + "nbformat": 4, + "nbformat_minor": 4 +} diff --git a/rwth_nb/misc/feedback.py b/rwth_nb/misc/feedback.py index 4d2e4a89570a5c99dbe934b34e8fcd60ba95f7d0..048dd71f829e9c8786bef95da6d5fcaac36a00e3 100644 --- a/rwth_nb/misc/feedback.py +++ b/rwth_nb/misc/feedback.py @@ -13,7 +13,6 @@ import subprocess # for hiding files on Windows import pandas as pd import logging - def get_notebook_name(): try: # import necessary packages (see https://github.com/jupyter/notebook/issues/1000) @@ -43,7 +42,8 @@ def get_notebook_name(): class RWTHFeedback: - """ RWTH Feedback submission class + """ + RWTH Feedback submission class Use as described in RWTH\ Misc.ipynb """ @@ -365,7 +365,6 @@ class RWTHFeedback: self.btn_submit.disabled = True self.btn_submit.description = self.feedback_text['sent'] - class RWTHCollector: """ RWTH Collector Class diff --git a/rwth_nb/misc/notebook.py b/rwth_nb/misc/notebook.py new file mode 100644 index 0000000000000000000000000000000000000000..ea334f1e78a64568d5260a9bde587f6a6c3ee54c --- /dev/null +++ b/rwth_nb/misc/notebook.py @@ -0,0 +1,28 @@ +import os +import requests +from IPython.lib import kernel +from notebook import notebookapp + +def get_contents(): + servers = list(notebookapp.list_running_servers()) + + token = servers[0]['token'] + api_url = servers[0]['url'] + 'api' + + connection_file_path = kernel.get_connection_file() + connection_file = os.path.basename(connection_file_path) + kernel_id = connection_file.split('-', 1)[1].split('.')[0] + + headers = { + 'Authorization': f'token {token}' + } + + r = requests.request('GET', f'{api_url}/sessions', headers=headers) + + for session in r.json(): + if session['kernel']['id'] == kernel_id: + path = session['notebook']['path'] + + r = requests.request('GET', f'{api_url}/contents/{path}', headers=headers) + + return r.json() \ No newline at end of file diff --git a/rwth_nb/misc/submission.py b/rwth_nb/misc/submission.py new file mode 100644 index 0000000000000000000000000000000000000000..92cb1615fce968b0c51a748d7665e3f8d16bf17b --- /dev/null +++ b/rwth_nb/misc/submission.py @@ -0,0 +1,134 @@ +import requests +import json +import os +import datetime as dt + +import rwth_nb.misc.notebook as notebook + + +class Submission: + """Submit and retrieve submissions to RWTH JupyterHub Service""" + + def __init__(self, realm): + self.realm = realm + + self.profile = os.environ.get('JUPYTERHUB_PROFILE') + self.token = os.environ.get('JUPYTERHUB_API_TOKEN') + + self.service_url = 'http://proxy-public.jhub/services/submission' + + if self.token is None or self.profile is None: + raise Exception('Submissions only work inside the RWTHjupyter cluster!') + + def url(self, user='me', ident=None): + url = f'{self.service_url}/{self.profile}/{self.realm}' + + if user is not None: + url += f'/{user}' + + if ident is not None: + url += f'/{ident}' + + return url + + def headers(self): + return { + 'Authorization': f'token {self.token}' + } + + def _parse_get(self, response): + payload = response.json() + + if 'data' in payload: + for data in payload['data']: + if 'submitted' in data: + data['submitted'] = dt.datetime.fromisoformat(data['submitted']) + + return payload + + def submit(self, data): + """Submit some arbitrary JSON payload""" + + r = requests.request('POST', + url=self.url(user=None), + data=json.dumps(data), + headers=self.headers()) + r.raise_for_status() + + def submit_notebook(self): + """Submit the notebook as JSON""" + + nb = notebook.get_contents() + + self.submit(nb) + + def is_submitted(self): + """Check if the user has already submitted something to this realm""" + + s = self.get() + + return len(s) > 0 + + def get(self, user='me', ident=None, limit=None): + """Get all submissions for a user""" + + params = {} + if limit is not None: + params['limit'] = limit + + r = requests.request('GET', url=self.url(user, ident), + headers=self.headers(), + params=params) + r.raise_for_status() + + j = self._parse_get(r) + + return j['submissions'] + + def fetch_notebook(self, user='me', ident=None, limit=None, subdir=None, prefix=None): + subs = self.get(user, ident, limit) + + for sub in subs: + nb = sub.pop('data') + + realm = sub['realm'] + submitter = sub['submitter'] + id = sub['id'] + name, ext = os.path.splitext(nb['name']) + + # Add submission details as metadata to the notebook + nb['content']['metadata']['submission'] = sub + + fn = f'{name}_{realm}_{submitter}-{id}{ext}' + + if prefix: + fn = f'{prefix}_{fn}' + + if subdir: + os.makedirs(subdir) + fn = f'{subdir}/{fn}' + + with open(fn, 'w+') as nb_file: + json.dump(nb['content'], nb_file) + + def fetch_all_notebooks(self): + """Get all submissions in a realm""" + + return self.fetch_notebook(user=None) + + def delete(self, user='me', ident=None): + """Delete the users submission""" + + r = requests.request('DELETE', url=self.url(user, ident), + headers=self.headers()) + r.raise_for_status() + + def get_all(self): + """Get all submissions in a realm""" + + return self.get(user=None) + + def delete_all(self): + """Delete all submissions""" + + return self.delete(user=None) \ No newline at end of file diff --git a/tests/submission.py b/tests/submission.py new file mode 100644 index 0000000000000000000000000000000000000000..9ba0825a8f240d56f88ed3f3790b7d23c7fdb844 --- /dev/null +++ b/tests/submission.py @@ -0,0 +1,20 @@ +# Testing + +from rwth_nb.misc.submission import Submission + +#os.environ['JUPYTERHUB_API_TOKEN'] = 'xxx' + +if __name__ == '__main__': + sub = Submission('test_realm') + + is_submitted = sub.is_submitted() + if is_submitted: + print('Evaluation already submitted') + else: + sub.submit({ + 'test': [1, 2, 3] + }) + + print('All submissions:') + for s in sub.get_all(): + print(s)