e2xauthoring 0.1.0__tar.gz → 0.2.0.dev0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/.pre-commit-config.yaml +9 -3
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/PKG-INFO +4 -3
- e2xauthoring-0.2.0.dev0/README.md +5 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/__version__.py +1 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/app/authoring.py +3 -4
- {e2xauthoring-0.1.0/e2xauthoring/app → e2xauthoring-0.2.0.dev0/e2xauthoring/app/handlers}/apihandlers.py +65 -18
- e2xauthoring-0.2.0.dev0/e2xauthoring/app/handlers/base.py +41 -0
- {e2xauthoring-0.1.0/e2xauthoring/app → e2xauthoring-0.2.0.dev0/e2xauthoring/app/handlers}/handlers.py +3 -3
- e2xauthoring-0.2.0.dev0/e2xauthoring/converters/__init__.py +4 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/converters/converter.py +28 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/converters/generateexercise.py +40 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/__init__.py +15 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/assignmentmodel.py +34 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/basemodel.py +20 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/dataclasses/__init__.py +28 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/dataclasses/message.py +13 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/dataclasses/template.py +6 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/exercisemodel.py +44 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/lister/lister.py +27 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/lister/taskpoollister.py +33 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/lister/templatelister.py +17 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/manager.py +40 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/taskmanager.py +123 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/taskpoolmanager.py +63 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/templatemanager.py +50 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presetmodel.py +71 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Code (Autograded).ipynb +92 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Code (Manual).ipynb +68 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Diagram.ipynb +73 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Freetext.ipynb +63 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Multiple Choice.ipynb +54 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Single Choice.ipynb +52 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Upload Files.ipynb +64 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Footer.ipynb +47 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Group Info.ipynb +42 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Header.ipynb +47 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Student Info.ipynb +41 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/taskmodel.py +186 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/models/taskpoolmodel.py +59 -2
- e2xauthoring-0.2.0.dev0/e2xauthoring/models/templatemodel.py +67 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/__init__.py +19 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/addtaskheader.py +98 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/copyfiles.py +91 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/copynotebooks.py +21 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/filltemplate.py +37 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/generatetaskids.py +60 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/makeexercise.py +73 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/preprocessor.py +10 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/removeexercise.py +19 -0
- e2xauthoring-0.2.0.dev0/e2xauthoring/utils/__init__.py +3 -0
- e2xauthoring-0.1.0/e2xauthoring/utils.py → e2xauthoring-0.2.0.dev0/e2xauthoring/utils/gitutils.py +14 -10
- e2xauthoring-0.2.0.dev0/e2xauthoring/utils/notebookvariableextractor.py +16 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/package-lock.json +6 -6
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/package.json +1 -1
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/pyproject.toml +43 -3
- e2xauthoring-0.1.0/README.md +0 -5
- e2xauthoring-0.1.0/e2xauthoring/__version__.py +0 -1
- e2xauthoring-0.1.0/e2xauthoring/models/__init__.py +0 -4
- e2xauthoring-0.1.0/e2xauthoring/models/taskmodel.py +0 -63
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/.github/workflows/sonarcloud.yml +0 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/.gitignore +0 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/LICENSE +0 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/MANIFEST.in +0 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/__init__.py +0 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/app/__init__.py +0 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/assets/.gitignore +0 -0
- {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/lerna.json +0 -0
|
@@ -1,20 +1,26 @@
|
|
|
1
1
|
repos:
|
|
2
2
|
- repo: https://github.com/psf/black
|
|
3
|
-
rev:
|
|
3
|
+
rev: 23.1.0
|
|
4
4
|
hooks:
|
|
5
5
|
- id: black
|
|
6
6
|
|
|
7
7
|
- repo: https://github.com/PyCQA/isort
|
|
8
|
-
rev: 5.
|
|
8
|
+
rev: 5.11.5
|
|
9
9
|
hooks:
|
|
10
10
|
- id: isort
|
|
11
11
|
files: \.py$
|
|
12
12
|
args: [--profile=black]
|
|
13
13
|
|
|
14
|
+
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
|
15
|
+
rev: "v0.0.267"
|
|
16
|
+
hooks:
|
|
17
|
+
- id: ruff
|
|
18
|
+
|
|
14
19
|
- repo: local
|
|
15
20
|
hooks:
|
|
16
21
|
- id: prettier
|
|
17
22
|
name: prettier
|
|
18
23
|
entry: 'npm run prettier:files'
|
|
19
24
|
language: node
|
|
20
|
-
types_or: [json, markdown, ts, tsx, javascript, jsx, css]
|
|
25
|
+
types_or: [json, markdown, ts, tsx, javascript, jsx, css]
|
|
26
|
+
exclude_types: [jupyter]
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.1
|
|
2
2
|
Name: e2xauthoring
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.2.0.dev0
|
|
4
4
|
Summary: Authoring app for e2xgrader
|
|
5
5
|
Project-URL: Source, https://github.com/Digiklausur/e2xauthoring
|
|
6
6
|
Author-email: Tim Metzler <tim.metzler@h-brs.de>
|
|
@@ -12,15 +12,16 @@ Classifier: Programming Language :: Python :: 3.7
|
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.8
|
|
13
13
|
Classifier: Programming Language :: Python :: 3.9
|
|
14
14
|
Requires-Python: >=3.7
|
|
15
|
-
Requires-Dist:
|
|
15
|
+
Requires-Dist: e2xcore==0.0.2
|
|
16
16
|
Requires-Dist: gitpython
|
|
17
17
|
Provides-Extra: dev
|
|
18
18
|
Requires-Dist: hatchling; extra == 'dev'
|
|
19
19
|
Requires-Dist: pre-commit; extra == 'dev'
|
|
20
|
+
Requires-Dist: tbump; extra == 'dev'
|
|
20
21
|
Description-Content-Type: text/markdown
|
|
21
22
|
|
|
22
23
|
# e2xauthoring
|
|
23
24
|
|
|
24
25
|
---
|
|
25
26
|
|
|
26
|
-
This is an authoring component for e2xgrader
|
|
27
|
+
This is an authoring component for e2xgrader
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.2.0-dev0"
|
|
@@ -1,15 +1,14 @@
|
|
|
1
1
|
import os
|
|
2
2
|
|
|
3
|
-
from
|
|
3
|
+
from e2xcore import BaseApp
|
|
4
4
|
from nbgrader.apps.baseapp import NbGrader
|
|
5
5
|
from tornado import web
|
|
6
6
|
|
|
7
|
-
from .apihandlers import default_handlers
|
|
8
|
-
from .handlers import default_handlers as handlers
|
|
7
|
+
from .handlers.apihandlers import default_handlers
|
|
8
|
+
from .handlers.handlers import default_handlers as handlers
|
|
9
9
|
|
|
10
10
|
|
|
11
11
|
class AuthoringApp(NbGrader, BaseApp):
|
|
12
|
-
|
|
13
12
|
template_path = os.path.join(os.path.dirname(__file__), "templates")
|
|
14
13
|
static_path = os.path.join(template_path, "authoring", "static")
|
|
15
14
|
|
|
@@ -1,27 +1,71 @@
|
|
|
1
1
|
import json
|
|
2
|
+
import os
|
|
2
3
|
|
|
3
|
-
from
|
|
4
|
-
from
|
|
5
|
-
from
|
|
6
|
-
BaseApiListHandler,
|
|
7
|
-
BaseApiManageHandler,
|
|
8
|
-
E2xApiHandler,
|
|
9
|
-
ExerciseModel,
|
|
10
|
-
KernelSpecHandler,
|
|
11
|
-
PresetHandler,
|
|
12
|
-
TemplateVariableHandler,
|
|
13
|
-
api_url,
|
|
14
|
-
assignment_regex,
|
|
15
|
-
name_regex,
|
|
16
|
-
pool_regex,
|
|
17
|
-
)
|
|
18
|
-
from e2xgrader.utils import urljoin
|
|
4
|
+
from e2xcore.handlers import E2xApiHandler
|
|
5
|
+
from e2xcore.utils import urljoin
|
|
6
|
+
from jupyter_client.kernelspec import KernelSpecManager
|
|
19
7
|
from nbgrader.server_extensions.formgrader.base import check_xsrf
|
|
20
8
|
from tornado import web
|
|
21
9
|
|
|
22
|
-
from
|
|
10
|
+
from ...converters import GenerateExercise
|
|
11
|
+
from ...models import (
|
|
12
|
+
AssignmentModel,
|
|
13
|
+
ExerciseModel,
|
|
14
|
+
PresetModel,
|
|
15
|
+
TaskModel,
|
|
16
|
+
TaskPoolModel,
|
|
17
|
+
TemplateModel,
|
|
18
|
+
)
|
|
19
|
+
from ...utils import NotebookVariableExtractor
|
|
20
|
+
from ...utils.gitutils import get_author, set_author
|
|
21
|
+
from .base import BaseApiListHandler, BaseApiManageHandler
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class PresetHandler(E2xApiHandler):
|
|
25
|
+
def initialize(self):
|
|
26
|
+
self.__model = PresetModel(self.coursedir)
|
|
27
|
+
|
|
28
|
+
def _list_template(self):
|
|
29
|
+
self.write(json.dumps(self.__model.list_template_presets()))
|
|
30
|
+
|
|
31
|
+
def _get_template(self):
|
|
32
|
+
name = self.get_argument("name")
|
|
33
|
+
self.write(json.dumps(self.__model.get_template_preset(name)))
|
|
34
|
+
|
|
35
|
+
def _list_question(self):
|
|
36
|
+
self.write(json.dumps(self.__model.list_question_presets()))
|
|
37
|
+
|
|
38
|
+
def _get_question(self):
|
|
39
|
+
name = self.get_argument("name")
|
|
40
|
+
self.write(json.dumps(self.__model.get_question_preset(name)))
|
|
41
|
+
|
|
42
|
+
@web.authenticated
|
|
43
|
+
@check_xsrf
|
|
44
|
+
def get(self):
|
|
45
|
+
action = self.get_argument("action")
|
|
46
|
+
preset_type = self.get_argument("type")
|
|
47
|
+
handler = getattr(self, "_{}_{}".format(action, preset_type))
|
|
48
|
+
handler()
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class TemplateVariableHandler(E2xApiHandler):
|
|
52
|
+
@web.authenticated
|
|
53
|
+
@check_xsrf
|
|
54
|
+
def get(self):
|
|
55
|
+
template = self.get_argument("template")
|
|
56
|
+
variables = NotebookVariableExtractor().extract(
|
|
57
|
+
os.path.join(
|
|
58
|
+
self.url_prefix, "templates", template, "{}.ipynb".format(template)
|
|
59
|
+
)
|
|
60
|
+
)
|
|
61
|
+
self.write(json.dumps(variables))
|
|
62
|
+
|
|
23
63
|
|
|
24
|
-
|
|
64
|
+
class KernelSpecHandler(E2xApiHandler):
|
|
65
|
+
@web.authenticated
|
|
66
|
+
@check_xsrf
|
|
67
|
+
def get(self):
|
|
68
|
+
self.write(json.dumps(KernelSpecManager().get_all_specs()))
|
|
25
69
|
|
|
26
70
|
|
|
27
71
|
class ManagePoolsHandler(E2xApiHandler):
|
|
@@ -185,6 +229,9 @@ class GitAuthorHandler(E2xApiHandler):
|
|
|
185
229
|
self.write(json.dumps(set_author(name=name, email=email)))
|
|
186
230
|
|
|
187
231
|
|
|
232
|
+
api_url = urljoin("e2x", "authoring", "api")
|
|
233
|
+
name_regex = r"(?P<name>[^/]+)"
|
|
234
|
+
assignment_regex = r"(?P<assignment>[^/]+)"
|
|
188
235
|
pool_action_regex = r"(?P<action>new|list|remove|get|init_repo)"
|
|
189
236
|
task_action_regex = r"(?P<action>new|list|list_all|remove|commit|get|diff)"
|
|
190
237
|
template_action_regex = r"(?P<action>new|list|remove)"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import json
|
|
2
|
+
|
|
3
|
+
from e2xcore.handlers import E2xApiHandler
|
|
4
|
+
from nbgrader.server_extensions.formgrader.base import check_xsrf
|
|
5
|
+
from tornado import web
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseApiManageHandler(E2xApiHandler):
|
|
9
|
+
def initialize(self, model_cls):
|
|
10
|
+
self.__model = model_cls(self.coursedir)
|
|
11
|
+
|
|
12
|
+
@web.authenticated
|
|
13
|
+
@check_xsrf
|
|
14
|
+
def post(self, **kwargs):
|
|
15
|
+
self.write(self.__model.new(**kwargs))
|
|
16
|
+
|
|
17
|
+
@web.authenticated
|
|
18
|
+
@check_xsrf
|
|
19
|
+
def delete(self, **kwargs):
|
|
20
|
+
self.__model.remove(**kwargs)
|
|
21
|
+
self.write({"status": True})
|
|
22
|
+
|
|
23
|
+
@web.authenticated
|
|
24
|
+
@check_xsrf
|
|
25
|
+
def get(self, **kwargs):
|
|
26
|
+
self.write(json.dumps(self.__model.get(**kwargs)))
|
|
27
|
+
|
|
28
|
+
@web.authenticated
|
|
29
|
+
@check_xsrf
|
|
30
|
+
def put(self, **kwargs):
|
|
31
|
+
self.write(self.__model.new(**kwargs))
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class BaseApiListHandler(E2xApiHandler):
|
|
35
|
+
def initialize(self, model_cls):
|
|
36
|
+
self.__model = model_cls(self.coursedir)
|
|
37
|
+
|
|
38
|
+
@web.authenticated
|
|
39
|
+
@check_xsrf
|
|
40
|
+
def get(self, **kwargs):
|
|
41
|
+
self.write(json.dumps(self.__model.list(**kwargs)))
|
|
@@ -1,9 +1,7 @@
|
|
|
1
1
|
import os
|
|
2
2
|
import sys
|
|
3
|
-
from email.policy import default
|
|
4
3
|
|
|
5
|
-
from
|
|
6
|
-
from e2xgrader.utils import urljoin
|
|
4
|
+
from e2xcore.utils import urljoin
|
|
7
5
|
from nbgrader.server_extensions.formgrader.base import (
|
|
8
6
|
BaseHandler,
|
|
9
7
|
check_notebook_dir,
|
|
@@ -11,6 +9,8 @@ from nbgrader.server_extensions.formgrader.base import (
|
|
|
11
9
|
)
|
|
12
10
|
from tornado import web
|
|
13
11
|
|
|
12
|
+
app_url = urljoin("e2x", "authoring", "app")
|
|
13
|
+
|
|
14
14
|
|
|
15
15
|
class AuthoringHandler(BaseHandler):
|
|
16
16
|
@web.authenticated
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from traitlets import List
|
|
2
|
+
from traitlets.config import Config, LoggingConfigurable
|
|
3
|
+
from traitlets.utils.importstring import import_item
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Converter(LoggingConfigurable):
|
|
7
|
+
preprocessors = List([], help="List of preprocessors for the converter")
|
|
8
|
+
|
|
9
|
+
def __init__(self, config=None):
|
|
10
|
+
with_default_config = self.default_config()
|
|
11
|
+
if config:
|
|
12
|
+
with_default_config.merge(config)
|
|
13
|
+
self.init_preprocessors()
|
|
14
|
+
|
|
15
|
+
def init_preprocessors(self):
|
|
16
|
+
self._preprocessors = []
|
|
17
|
+
for preprocessor in self.preprocessors:
|
|
18
|
+
if isinstance(preprocessor, type):
|
|
19
|
+
self._preprocessors.append(preprocessor())
|
|
20
|
+
else:
|
|
21
|
+
self._preprocessors.append(import_item(preprocessor)())
|
|
22
|
+
|
|
23
|
+
def default_config(self):
|
|
24
|
+
return Config()
|
|
25
|
+
|
|
26
|
+
def convert(self, resources):
|
|
27
|
+
for preprocessor in self._preprocessors:
|
|
28
|
+
preprocessor.preprocess(resources)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import tempfile
|
|
2
|
+
|
|
3
|
+
from traitlets import List
|
|
4
|
+
|
|
5
|
+
from ..preprocessors import (
|
|
6
|
+
AddTaskHeader,
|
|
7
|
+
CopyFiles,
|
|
8
|
+
CopyNotebooks,
|
|
9
|
+
FillTemplate,
|
|
10
|
+
GenerateTaskIDs,
|
|
11
|
+
MakeExercise,
|
|
12
|
+
RemoveExercise,
|
|
13
|
+
)
|
|
14
|
+
from .converter import Converter
|
|
15
|
+
|
|
16
|
+
|
|
17
|
+
class GenerateExercise(Converter):
|
|
18
|
+
preprocessors = List(
|
|
19
|
+
[
|
|
20
|
+
RemoveExercise,
|
|
21
|
+
CopyNotebooks,
|
|
22
|
+
FillTemplate,
|
|
23
|
+
CopyFiles,
|
|
24
|
+
GenerateTaskIDs,
|
|
25
|
+
AddTaskHeader,
|
|
26
|
+
MakeExercise,
|
|
27
|
+
]
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
def __init__(self, coursedir, config=None):
|
|
31
|
+
super(GenerateExercise, self).__init__(config=config)
|
|
32
|
+
self.coursedir = coursedir
|
|
33
|
+
|
|
34
|
+
def convert(self, resources):
|
|
35
|
+
with tempfile.TemporaryDirectory() as tmp:
|
|
36
|
+
resources["tmp_dir"] = tmp
|
|
37
|
+
resources["course_prefix"] = self.coursedir.root
|
|
38
|
+
resources["source_dir"] = self.coursedir.source_directory
|
|
39
|
+
for preprocessor in self._preprocessors:
|
|
40
|
+
preprocessor.preprocess(resources)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from .assignmentmodel import AssignmentModel
|
|
2
|
+
from .exercisemodel import ExerciseModel
|
|
3
|
+
from .presetmodel import PresetModel
|
|
4
|
+
from .taskmodel import TaskModel
|
|
5
|
+
from .taskpoolmodel import TaskPoolModel
|
|
6
|
+
from .templatemodel import TemplateModel
|
|
7
|
+
|
|
8
|
+
__all__ = [
|
|
9
|
+
"AssignmentModel",
|
|
10
|
+
"ExerciseModel",
|
|
11
|
+
"PresetModel",
|
|
12
|
+
"TaskModel",
|
|
13
|
+
"TaskPoolModel",
|
|
14
|
+
"TemplateModel",
|
|
15
|
+
]
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
|
|
4
|
+
from traitlets import Unicode
|
|
5
|
+
|
|
6
|
+
from .basemodel import BaseModel
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class AssignmentModel(BaseModel):
|
|
10
|
+
directory = Unicode("source", help="The directory where assignments go.")
|
|
11
|
+
|
|
12
|
+
def __get_assignment_info(self, assignment):
|
|
13
|
+
return len(glob.glob(os.path.join(self.base_path(), assignment, "*.ipynb")))
|
|
14
|
+
|
|
15
|
+
def list(self, **kwargs):
|
|
16
|
+
if not os.path.isdir(self.base_path()):
|
|
17
|
+
os.makedirs(self.base_path(), exist_ok=True)
|
|
18
|
+
assignmentfolders = os.listdir(self.base_path())
|
|
19
|
+
assignments = []
|
|
20
|
+
for assignmentfolder in assignmentfolders:
|
|
21
|
+
if assignmentfolder.startswith("."):
|
|
22
|
+
continue
|
|
23
|
+
exercises = self.__get_assignment_info(assignmentfolder)
|
|
24
|
+
assignments.append(
|
|
25
|
+
{
|
|
26
|
+
"name": assignmentfolder,
|
|
27
|
+
"exercises": exercises,
|
|
28
|
+
"link": os.path.join(
|
|
29
|
+
"taskcreator", "assignments", assignmentfolder
|
|
30
|
+
),
|
|
31
|
+
}
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return assignments
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
from e2xcore.utils.utils import get_nbgrader_config
|
|
4
|
+
from traitlets import Unicode
|
|
5
|
+
from traitlets.config import LoggingConfigurable
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class BaseModel(LoggingConfigurable):
|
|
9
|
+
directory = Unicode(".", help="The directory of the model")
|
|
10
|
+
|
|
11
|
+
def __init__(self, coursedir):
|
|
12
|
+
self.coursedir = coursedir
|
|
13
|
+
self.__pattern = re.compile(r"^\w+[\w\s]*\w+$")
|
|
14
|
+
self.config = get_nbgrader_config()
|
|
15
|
+
|
|
16
|
+
def base_path(self):
|
|
17
|
+
return self.coursedir.format_path(self.directory, ".", ".")
|
|
18
|
+
|
|
19
|
+
def is_valid_name(self, name):
|
|
20
|
+
return self.__pattern.match(name) is not None
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
from typing import Dict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
@dataclass
|
|
6
|
+
class Template:
|
|
7
|
+
name: str
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
@dataclass
|
|
11
|
+
class TaskPool:
|
|
12
|
+
name: str
|
|
13
|
+
n_tasks: int
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass
|
|
17
|
+
class Task:
|
|
18
|
+
name: str
|
|
19
|
+
pool: str
|
|
20
|
+
points: int
|
|
21
|
+
n_questions: int
|
|
22
|
+
git_status: Dict
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
@dataclass
|
|
26
|
+
class Exercise:
|
|
27
|
+
name: str
|
|
28
|
+
assignment: str
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import glob
|
|
2
|
+
import os
|
|
3
|
+
import shutil
|
|
4
|
+
|
|
5
|
+
from traitlets import Unicode
|
|
6
|
+
|
|
7
|
+
from .basemodel import BaseModel
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class ExerciseModel(BaseModel):
|
|
11
|
+
directory = Unicode("source", help="The directory where assignments go.")
|
|
12
|
+
|
|
13
|
+
def get(self, **kwargs):
|
|
14
|
+
return {"name": kwargs["name"], "assignment": kwargs["assignment"]}
|
|
15
|
+
|
|
16
|
+
def remove(self, **kwargs):
|
|
17
|
+
assignment = kwargs["assignment"]
|
|
18
|
+
name = kwargs["name"]
|
|
19
|
+
base_path = os.path.join(self.base_path(), assignment)
|
|
20
|
+
exercise_files = os.path.join(base_path, "{}_files".format(name))
|
|
21
|
+
if os.path.exists(exercise_files):
|
|
22
|
+
shutil.rmtree(exercise_files)
|
|
23
|
+
exercise = os.path.join(base_path, "{}.ipynb".format(name))
|
|
24
|
+
if os.path.exists(exercise):
|
|
25
|
+
os.remove(exercise)
|
|
26
|
+
|
|
27
|
+
def list(self, **kwargs):
|
|
28
|
+
assignment = kwargs["assignment"]
|
|
29
|
+
base_path = os.path.join(self.base_path(), assignment)
|
|
30
|
+
exercisenbs = glob.glob(os.path.join(base_path, "*.ipynb"))
|
|
31
|
+
exercises = []
|
|
32
|
+
for exercisenb in exercisenbs:
|
|
33
|
+
name = os.path.split(exercisenb)[-1].replace(".ipynb", "")
|
|
34
|
+
exercises.append(
|
|
35
|
+
{
|
|
36
|
+
"name": name,
|
|
37
|
+
"assignment": assignment,
|
|
38
|
+
"link": os.path.join(
|
|
39
|
+
"taskcreator", "assignments", assignment, name
|
|
40
|
+
),
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
|
|
44
|
+
return exercises
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from abc import abstractmethod
|
|
3
|
+
from typing import List
|
|
4
|
+
|
|
5
|
+
from nbgrader.coursedir import CourseDirectory
|
|
6
|
+
from traitlets import Unicode
|
|
7
|
+
from traitlets.config import LoggingConfigurable
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Lister(LoggingConfigurable):
|
|
11
|
+
directory = Unicode(".", help="The directory of the items to list")
|
|
12
|
+
|
|
13
|
+
def __init__(self, coursedir: CourseDirectory) -> None:
|
|
14
|
+
self.coursedir = coursedir
|
|
15
|
+
|
|
16
|
+
@property
|
|
17
|
+
def base_path(self):
|
|
18
|
+
return self.coursedir.format_path(self.directory, ".", ".")
|
|
19
|
+
|
|
20
|
+
def listdir(self, path: str) -> List[str]:
|
|
21
|
+
return [
|
|
22
|
+
directory for directory in os.listdir(path) if not directory.startswith(".")
|
|
23
|
+
]
|
|
24
|
+
|
|
25
|
+
@abstractmethod
|
|
26
|
+
def list_items(self, **kwargs):
|
|
27
|
+
pass
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import os
|
|
2
|
+
from typing import List
|
|
3
|
+
|
|
4
|
+
from traitlets import Unicode
|
|
5
|
+
|
|
6
|
+
from ...utils.gitutils import is_version_controlled
|
|
7
|
+
from ..dataclasses import TaskPool
|
|
8
|
+
from .lister import Lister
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class TaskPoolLister(Lister):
|
|
12
|
+
directory = Unicode(
|
|
13
|
+
"pools", help="The relative directory where the pools are stored"
|
|
14
|
+
)
|
|
15
|
+
|
|
16
|
+
def __get_n_tasks(self, name):
|
|
17
|
+
return len(
|
|
18
|
+
[
|
|
19
|
+
d
|
|
20
|
+
for d in os.listdir(os.path.join(self.base_path, name))
|
|
21
|
+
if not d.startswith(".")
|
|
22
|
+
]
|
|
23
|
+
)
|
|
24
|
+
|
|
25
|
+
def list_items(self, **kwargs) -> List[TaskPool]:
|
|
26
|
+
return [
|
|
27
|
+
TaskPool(
|
|
28
|
+
name=pool_dir,
|
|
29
|
+
n_tasks=self.__get_n_tasks(pool_dir),
|
|
30
|
+
is_repo=is_version_controlled(os.path.join(self.base_path, pool_dir)),
|
|
31
|
+
)
|
|
32
|
+
for pool_dir in self.listdir(self.base_path)
|
|
33
|
+
]
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from typing import List
|
|
2
|
+
|
|
3
|
+
from traitlets import Unicode
|
|
4
|
+
|
|
5
|
+
from ..dataclasses.template import Template
|
|
6
|
+
from .lister import Lister
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class TemplateLister(Lister):
|
|
10
|
+
directory = Unicode(
|
|
11
|
+
"templates", help="The relative directory where the templates are stored"
|
|
12
|
+
)
|
|
13
|
+
|
|
14
|
+
def list_items(self, **kwargs) -> List[Template]:
|
|
15
|
+
return [
|
|
16
|
+
Template(name=template_dir) for template_dir in self.listdir(self.base_path)
|
|
17
|
+
]
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import re
|
|
3
|
+
from abc import abstractmethod
|
|
4
|
+
from typing import List
|
|
5
|
+
|
|
6
|
+
from nbgrader.coursedir import CourseDirectory
|
|
7
|
+
from traitlets import Unicode
|
|
8
|
+
from traitlets.config import LoggingConfigurable
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class BaseManager(LoggingConfigurable):
|
|
12
|
+
directory = Unicode(".", help="The directory of the items to manage")
|
|
13
|
+
|
|
14
|
+
def __init__(self, coursedir: CourseDirectory) -> None:
|
|
15
|
+
self.coursedir = coursedir
|
|
16
|
+
self.__pattern = re.compile(r"^\w+[\w\s]*\w+$")
|
|
17
|
+
|
|
18
|
+
@property
|
|
19
|
+
def base_path(self):
|
|
20
|
+
return self.coursedir.format_path(self.directory, ".", ".")
|
|
21
|
+
|
|
22
|
+
def is_valid_name(self, name):
|
|
23
|
+
return self.__pattern.match(name) is not None
|
|
24
|
+
|
|
25
|
+
def listdir(self, path: str) -> List[str]:
|
|
26
|
+
return [
|
|
27
|
+
directory for directory in os.listdir(path) if not directory.startswith(".")
|
|
28
|
+
]
|
|
29
|
+
|
|
30
|
+
@abstractmethod
|
|
31
|
+
def create(self, **kwargs):
|
|
32
|
+
pass
|
|
33
|
+
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def remove(self, **kwargs):
|
|
36
|
+
pass
|
|
37
|
+
|
|
38
|
+
@abstractmethod
|
|
39
|
+
def list(self, **kwargs):
|
|
40
|
+
pass
|