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.
Files changed (67) hide show
  1. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/.pre-commit-config.yaml +9 -3
  2. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/PKG-INFO +4 -3
  3. e2xauthoring-0.2.0.dev0/README.md +5 -0
  4. e2xauthoring-0.2.0.dev0/e2xauthoring/__version__.py +1 -0
  5. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/app/authoring.py +3 -4
  6. {e2xauthoring-0.1.0/e2xauthoring/app → e2xauthoring-0.2.0.dev0/e2xauthoring/app/handlers}/apihandlers.py +65 -18
  7. e2xauthoring-0.2.0.dev0/e2xauthoring/app/handlers/base.py +41 -0
  8. {e2xauthoring-0.1.0/e2xauthoring/app → e2xauthoring-0.2.0.dev0/e2xauthoring/app/handlers}/handlers.py +3 -3
  9. e2xauthoring-0.2.0.dev0/e2xauthoring/converters/__init__.py +4 -0
  10. e2xauthoring-0.2.0.dev0/e2xauthoring/converters/converter.py +28 -0
  11. e2xauthoring-0.2.0.dev0/e2xauthoring/converters/generateexercise.py +40 -0
  12. e2xauthoring-0.2.0.dev0/e2xauthoring/models/__init__.py +15 -0
  13. e2xauthoring-0.2.0.dev0/e2xauthoring/models/assignmentmodel.py +34 -0
  14. e2xauthoring-0.2.0.dev0/e2xauthoring/models/basemodel.py +20 -0
  15. e2xauthoring-0.2.0.dev0/e2xauthoring/models/dataclasses/__init__.py +28 -0
  16. e2xauthoring-0.2.0.dev0/e2xauthoring/models/dataclasses/message.py +13 -0
  17. e2xauthoring-0.2.0.dev0/e2xauthoring/models/dataclasses/template.py +6 -0
  18. e2xauthoring-0.2.0.dev0/e2xauthoring/models/exercisemodel.py +44 -0
  19. e2xauthoring-0.2.0.dev0/e2xauthoring/models/lister/lister.py +27 -0
  20. e2xauthoring-0.2.0.dev0/e2xauthoring/models/lister/taskpoollister.py +33 -0
  21. e2xauthoring-0.2.0.dev0/e2xauthoring/models/lister/templatelister.py +17 -0
  22. e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/manager.py +40 -0
  23. e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/taskmanager.py +123 -0
  24. e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/taskpoolmanager.py +63 -0
  25. e2xauthoring-0.2.0.dev0/e2xauthoring/models/managers/templatemanager.py +50 -0
  26. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presetmodel.py +71 -0
  27. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Code (Autograded).ipynb +92 -0
  28. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Code (Manual).ipynb +68 -0
  29. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Diagram.ipynb +73 -0
  30. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Freetext.ipynb +63 -0
  31. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Multiple Choice.ipynb +54 -0
  32. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Single Choice.ipynb +52 -0
  33. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/questions/Upload Files.ipynb +64 -0
  34. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Footer.ipynb +47 -0
  35. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Group Info.ipynb +42 -0
  36. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Header.ipynb +47 -0
  37. e2xauthoring-0.2.0.dev0/e2xauthoring/models/presets/template/Student Info.ipynb +41 -0
  38. e2xauthoring-0.2.0.dev0/e2xauthoring/models/taskmodel.py +186 -0
  39. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/models/taskpoolmodel.py +59 -2
  40. e2xauthoring-0.2.0.dev0/e2xauthoring/models/templatemodel.py +67 -0
  41. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/__init__.py +19 -0
  42. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/addtaskheader.py +98 -0
  43. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/copyfiles.py +91 -0
  44. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/copynotebooks.py +21 -0
  45. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/filltemplate.py +37 -0
  46. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/generatetaskids.py +60 -0
  47. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/makeexercise.py +73 -0
  48. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/preprocessor.py +10 -0
  49. e2xauthoring-0.2.0.dev0/e2xauthoring/preprocessors/removeexercise.py +19 -0
  50. e2xauthoring-0.2.0.dev0/e2xauthoring/utils/__init__.py +3 -0
  51. e2xauthoring-0.1.0/e2xauthoring/utils.py → e2xauthoring-0.2.0.dev0/e2xauthoring/utils/gitutils.py +14 -10
  52. e2xauthoring-0.2.0.dev0/e2xauthoring/utils/notebookvariableextractor.py +16 -0
  53. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/package-lock.json +6 -6
  54. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/package.json +1 -1
  55. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/pyproject.toml +43 -3
  56. e2xauthoring-0.1.0/README.md +0 -5
  57. e2xauthoring-0.1.0/e2xauthoring/__version__.py +0 -1
  58. e2xauthoring-0.1.0/e2xauthoring/models/__init__.py +0 -4
  59. e2xauthoring-0.1.0/e2xauthoring/models/taskmodel.py +0 -63
  60. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/.github/workflows/sonarcloud.yml +0 -0
  61. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/.gitignore +0 -0
  62. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/LICENSE +0 -0
  63. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/MANIFEST.in +0 -0
  64. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/__init__.py +0 -0
  65. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/app/__init__.py +0 -0
  66. {e2xauthoring-0.1.0 → e2xauthoring-0.2.0.dev0}/e2xauthoring/assets/.gitignore +0 -0
  67. {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: 22.6.0
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.10.1
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.1.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: e2xgrader==0.1.*
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,5 @@
1
+ # e2xauthoring
2
+
3
+ ---
4
+
5
+ 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 e2xgrader.server_extensions.grader.apps.base import BaseApp
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 e2xgrader.converters import GenerateExercise
4
- from e2xgrader.models import AssignmentModel, TemplateModel
5
- from e2xgrader.server_extensions.grader.apps.authoring.apihandlers import (
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 e2xauthoring.utils import get_author, set_author
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
- from ..models import TaskModel, TaskPoolModel
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 e2xgrader.server_extensions.grader.apps.authoring.handlers import app_url
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,4 @@
1
+ from .converter import Converter
2
+ from .generateexercise import GenerateExercise
3
+
4
+ __all__ = ["Converter", "GenerateExercise"]
@@ -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,13 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class SuccessMessage:
6
+ success: bool = True
7
+ message: str = ""
8
+
9
+
10
+ @dataclass
11
+ class ErrorMessage:
12
+ success: bool = False
13
+ error: str = ""
@@ -0,0 +1,6 @@
1
+ from dataclasses import dataclass
2
+
3
+
4
+ @dataclass
5
+ class Template:
6
+ name: 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