e2xauthoring 0.2.0.dev1__py3-none-any.whl → 0.2.0.dev2__py3-none-any.whl

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 (55) hide show
  1. e2xauthoring/__version__.py +1 -1
  2. e2xauthoring/app/handlers/apihandlers.py +76 -219
  3. e2xauthoring/app/handlers/base.py +83 -22
  4. e2xauthoring/app/templates/authoring/asset-manifest.json +3 -3
  5. e2xauthoring/app/templates/authoring/index.html +1 -1
  6. e2xauthoring/app/templates/authoring/static/js/main.1e6f65c1.js +3 -0
  7. e2xauthoring/app/templates/authoring/static/js/main.1e6f65c1.js.map +1 -0
  8. e2xauthoring/managers/__init__.py +17 -0
  9. e2xauthoring/{models/assignmentmodel.py → managers/assignmentmanager.py} +7 -7
  10. e2xauthoring/{models/managers/manager.py → managers/base.py} +19 -1
  11. e2xauthoring/managers/dataclasses/__init__.py +49 -0
  12. e2xauthoring/{models/presetmodel.py → managers/presetmanager.py} +11 -5
  13. e2xauthoring/managers/taskmanager.py +167 -0
  14. e2xauthoring/managers/taskpoolmanager.py +66 -0
  15. e2xauthoring/managers/templatemanager.py +69 -0
  16. e2xauthoring/managers/worksheetmanager.py +50 -0
  17. e2xauthoring/preprocessors/addtaskheader.py +1 -1
  18. e2xauthoring/preprocessors/copyfiles.py +5 -4
  19. e2xauthoring/preprocessors/copynotebooks.py +6 -5
  20. e2xauthoring/preprocessors/filltemplate.py +2 -0
  21. e2xauthoring/preprocessors/makeexercise.py +9 -2
  22. {e2xauthoring-0.2.0.dev1.dist-info → e2xauthoring-0.2.0.dev2.dist-info}/METADATA +1 -1
  23. e2xauthoring-0.2.0.dev2.dist-info/RECORD +52 -0
  24. e2xauthoring/app/templates/authoring/static/js/main.fe9bcd55.js +0 -3
  25. e2xauthoring/app/templates/authoring/static/js/main.fe9bcd55.js.map +0 -1
  26. e2xauthoring/models/__init__.py +0 -15
  27. e2xauthoring/models/basemodel.py +0 -20
  28. e2xauthoring/models/dataclasses/__init__.py +0 -28
  29. e2xauthoring/models/dataclasses/message.py +0 -13
  30. e2xauthoring/models/dataclasses/template.py +0 -6
  31. e2xauthoring/models/exercisemodel.py +0 -44
  32. e2xauthoring/models/lister/lister.py +0 -27
  33. e2xauthoring/models/lister/taskpoollister.py +0 -33
  34. e2xauthoring/models/lister/templatelister.py +0 -17
  35. e2xauthoring/models/managers/taskmanager.py +0 -123
  36. e2xauthoring/models/managers/taskpoolmanager.py +0 -63
  37. e2xauthoring/models/managers/templatemanager.py +0 -50
  38. e2xauthoring/models/taskmodel.py +0 -186
  39. e2xauthoring/models/taskpoolmodel.py +0 -125
  40. e2xauthoring/models/templatemodel.py +0 -67
  41. e2xauthoring-0.2.0.dev1.dist-info/RECORD +0 -61
  42. /e2xauthoring/app/templates/authoring/static/js/{main.fe9bcd55.js.LICENSE.txt → main.1e6f65c1.js.LICENSE.txt} +0 -0
  43. /e2xauthoring/{models → managers}/presets/questions/Code (Autograded).ipynb +0 -0
  44. /e2xauthoring/{models → managers}/presets/questions/Code (Manual).ipynb +0 -0
  45. /e2xauthoring/{models → managers}/presets/questions/Diagram.ipynb +0 -0
  46. /e2xauthoring/{models → managers}/presets/questions/Freetext.ipynb +0 -0
  47. /e2xauthoring/{models → managers}/presets/questions/Multiple Choice.ipynb +0 -0
  48. /e2xauthoring/{models → managers}/presets/questions/Single Choice.ipynb +0 -0
  49. /e2xauthoring/{models → managers}/presets/questions/Upload Files.ipynb +0 -0
  50. /e2xauthoring/{models → managers}/presets/template/Footer.ipynb +0 -0
  51. /e2xauthoring/{models → managers}/presets/template/Group Info.ipynb +0 -0
  52. /e2xauthoring/{models → managers}/presets/template/Header.ipynb +0 -0
  53. /e2xauthoring/{models → managers}/presets/template/Student Info.ipynb +0 -0
  54. {e2xauthoring-0.2.0.dev1.dist-info → e2xauthoring-0.2.0.dev2.dist-info}/WHEEL +0 -0
  55. {e2xauthoring-0.2.0.dev1.dist-info → e2xauthoring-0.2.0.dev2.dist-info}/licenses/LICENSE +0 -0
@@ -1 +1 @@
1
- __version__ = "0.2.0-dev1"
1
+ __version__ = "0.2.0-dev2"
@@ -1,5 +1,4 @@
1
1
  import json
2
- import os
3
2
 
4
3
  from e2xcore.handlers import E2xApiHandler
5
4
  from e2xcore.utils import urljoin
@@ -7,58 +6,16 @@ from jupyter_client.kernelspec import KernelSpecManager
7
6
  from nbgrader.server_extensions.formgrader.base import check_xsrf
8
7
  from tornado import web
9
8
 
10
- from ...converters import GenerateExercise
11
- from ...models import (
12
- AssignmentModel,
13
- ExerciseModel,
14
- PresetModel,
15
- TaskModel,
16
- TaskPoolModel,
17
- TemplateModel,
9
+ from ...managers import (
10
+ AssignmentManager,
11
+ PresetManager,
12
+ TaskManager,
13
+ TaskPoolManager,
14
+ TemplateManager,
15
+ WorksheetManager,
18
16
  )
19
- from ...utils import NotebookVariableExtractor
20
17
  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))
18
+ from .base import ApiManageHandler
62
19
 
63
20
 
64
21
  class KernelSpecHandler(E2xApiHandler):
@@ -68,152 +25,6 @@ class KernelSpecHandler(E2xApiHandler):
68
25
  self.write(json.dumps(KernelSpecManager().get_all_specs()))
69
26
 
70
27
 
71
- class ManagePoolsHandler(E2xApiHandler):
72
- def initialize(self) -> None:
73
- self.__poolmodel = TaskPoolModel(self.coursedir)
74
-
75
- @web.authenticated
76
- @check_xsrf
77
- def get(self, action):
78
- if action == "list":
79
- self.write(json.dumps(self.__poolmodel.list()))
80
- elif action == "get":
81
- self.write(json.dumps(self.__poolmodel.get(pool=self.get_argument("pool"))))
82
- else:
83
- self.write(
84
- json.dumps(
85
- dict(status="error", message=f"{action} is not a valid action")
86
- )
87
- )
88
-
89
- @web.authenticated
90
- @check_xsrf
91
- def post(self, action):
92
- data = self.get_json_body()
93
- name = data.get("name")
94
- if action == "new":
95
- create_repository = data.get("create_repository", False)
96
- self.write(
97
- json.dumps(
98
- self.__poolmodel.new(name=name, create_repository=create_repository)
99
- )
100
- )
101
- elif action == "remove":
102
- self.write(json.dumps(self.__poolmodel.remove(name=name)))
103
- elif action == "init_repo":
104
- self.write(json.dumps(self.__poolmodel.turn_into_repository(name)))
105
- else:
106
- self.write(
107
- json.dumps(
108
- dict(status="error", message=f"{action} is not a valid action")
109
- )
110
- )
111
-
112
-
113
- class ManageTasksHandler(E2xApiHandler):
114
- def initialize(self) -> None:
115
- self.__model = TaskModel(self.coursedir)
116
-
117
- @web.authenticated
118
- @check_xsrf
119
- def get(self, action):
120
- if action == "list":
121
- self.write(json.dumps(self.__model.list(pool=self.get_argument("pool"))))
122
- elif action == "list_all":
123
- tasks = []
124
- pool_model = TaskPoolModel(self.coursedir)
125
- for pool in pool_model.list():
126
- tasks.extend(self.__model.list(pool["name"]))
127
- self.write(json.dumps(tasks))
128
- elif action == "get":
129
- self.write(
130
- json.dumps(
131
- self.__model.get(
132
- pool=self.get_argument("pool"), name=self.get_argument("name")
133
- )
134
- )
135
- )
136
- elif action == "diff":
137
- self.write(
138
- json.dumps(
139
- self.__model.git_diff(
140
- pool=self.get_argument("pool"),
141
- task=self.get_argument("task"),
142
- file=self.get_argument("file"),
143
- )
144
- )
145
- )
146
- else:
147
- self.write(
148
- json.dumps(
149
- dict(status="error", message=f"{action} is not a valid action")
150
- )
151
- )
152
-
153
- @web.authenticated
154
- @check_xsrf
155
- def post(self, action):
156
- data = self.get_json_body()
157
- name = data.get("name")
158
- pool = data.get("pool")
159
- if action == "new":
160
- self.write(json.dumps(self.__model.new(pool=pool, name=name)))
161
- elif action == "remove":
162
- self.write(json.dumps(self.__model.remove(pool=pool, name=name)))
163
- elif action == "commit":
164
- message = data.get("message", None)
165
- self.write(
166
- json.dumps(self.__model.commit(pool=pool, task=name, message=message))
167
- )
168
- else:
169
- self.write(
170
- json.dumps(
171
- dict(status="error", message=f"{action} is not a valid action")
172
- )
173
- )
174
-
175
-
176
- class ManageTemplatesHandler(E2xApiHandler):
177
- def initialize(self) -> None:
178
- self.__model = TemplateModel(self.coursedir)
179
-
180
- @web.authenticated
181
- @check_xsrf
182
- def get(self, action):
183
- if action == "list":
184
- self.write(json.dumps(self.__model.list()))
185
- else:
186
- self.write(
187
- json.dumps(
188
- dict(status="error", message=f"{action} is not a valid action")
189
- )
190
- )
191
-
192
- @web.authenticated
193
- @check_xsrf
194
- def post(self, action):
195
- data = self.get_json_body()
196
- name = data.get("name")
197
- if action == "new":
198
- self.write(json.dumps(self.__model.new(name=name)))
199
- elif action == "remove":
200
- self.write(json.dumps(self.__model.remove(name=name)))
201
- else:
202
- self.write(
203
- json.dumps(
204
- dict(status="error", message=f"{action} is not a valid action")
205
- )
206
- )
207
-
208
-
209
- class GenerateExerciseHandler(E2xApiHandler):
210
- @web.authenticated
211
- @check_xsrf
212
- def post(self):
213
- GenerateExercise(coursedir=self.coursedir).convert(self.get_json_body())
214
- self.write(dict(success=True))
215
-
216
-
217
28
  class GitAuthorHandler(E2xApiHandler):
218
29
  @web.authenticated
219
30
  @check_xsrf
@@ -232,41 +43,87 @@ class GitAuthorHandler(E2xApiHandler):
232
43
  api_url = urljoin("e2x", "authoring", "api")
233
44
  name_regex = r"(?P<name>[^/]+)"
234
45
  assignment_regex = r"(?P<assignment>[^/]+)"
235
- pool_action_regex = r"(?P<action>new|list|remove|get|init_repo)"
236
- task_action_regex = r"(?P<action>new|list|list_all|remove|commit|get|diff)"
237
- template_action_regex = r"(?P<action>new|list|remove)"
238
46
  default_handlers = [
239
- (urljoin(api_url, "presets"), PresetHandler),
240
47
  (
241
48
  urljoin(api_url, "assignments", "?"),
242
- BaseApiListHandler,
243
- dict(model_cls=AssignmentModel),
49
+ ApiManageHandler,
50
+ dict(
51
+ manager_cls=AssignmentManager,
52
+ actions=dict(
53
+ get=dict(actions=["list"]),
54
+ ),
55
+ ),
244
56
  ),
245
57
  (
246
- urljoin(api_url, "template", name_regex, "?"),
247
- BaseApiManageHandler,
248
- dict(model_cls=TemplateModel),
58
+ urljoin(api_url, "worksheets", "?"),
59
+ ApiManageHandler,
60
+ dict(
61
+ manager_cls=WorksheetManager,
62
+ actions=dict(
63
+ get=dict(actions=["get", "list"]),
64
+ delete=dict(default="remove", actions=["remove"]),
65
+ post=dict(default="create", actions=["create"]),
66
+ ),
67
+ ),
68
+ ),
69
+ (
70
+ urljoin(api_url, "tasks", "?"),
71
+ ApiManageHandler,
72
+ dict(
73
+ manager_cls=TaskManager,
74
+ actions=dict(
75
+ delete=dict(default="remove", actions=["remove"]),
76
+ get=dict(
77
+ default="get", actions=["get", "list", "list_all", "git_diff"]
78
+ ),
79
+ post=dict(default="create", actions=["create"]),
80
+ put=dict(actions=["copy", "rename", "commit"]),
81
+ ),
82
+ ),
249
83
  ),
250
84
  (
251
85
  urljoin(api_url, "templates", "?"),
252
- BaseApiListHandler,
253
- dict(model_cls=TemplateModel),
86
+ ApiManageHandler,
87
+ dict(
88
+ manager_cls=TemplateManager,
89
+ actions=dict(
90
+ delete=dict(default="remove", actions=["remove"]),
91
+ get=dict(default="get", actions=["get", "list", "list_variables"]),
92
+ post=dict(default="create", actions=["create"]),
93
+ put=dict(actions=["copy", "rename"]),
94
+ ),
95
+ ),
254
96
  ),
255
97
  (
256
- urljoin(api_url, "worksheets", assignment_regex, name_regex, "?"),
257
- BaseApiManageHandler,
258
- dict(model_cls=ExerciseModel),
98
+ urljoin(api_url, "pools", "?"),
99
+ ApiManageHandler,
100
+ dict(
101
+ manager_cls=TaskPoolManager,
102
+ actions=dict(
103
+ delete=dict(default="remove", actions=["remove"]),
104
+ get=dict(default="get", actions=["get", "list"]),
105
+ post=dict(default="create", actions=["create"]),
106
+ put=dict(actions=["copy", "rename", "turn_into_repository"]),
107
+ ),
108
+ ),
259
109
  ),
260
110
  (
261
- urljoin(api_url, "worksheets", assignment_regex, "?"),
262
- BaseApiListHandler,
263
- dict(model_cls=ExerciseModel),
111
+ urljoin(api_url, "presets", "?"),
112
+ ApiManageHandler,
113
+ dict(
114
+ manager_cls=PresetManager,
115
+ actions=dict(
116
+ get=dict(
117
+ actions=[
118
+ "list_question_presets",
119
+ "list_template_presets",
120
+ "get_question_preset",
121
+ "get_template_preset",
122
+ ]
123
+ )
124
+ ),
125
+ ),
264
126
  ),
265
- (urljoin(api_url, "pools", pool_action_regex, "?"), ManagePoolsHandler),
266
- (urljoin(api_url, "tasks", task_action_regex, "?"), ManageTasksHandler),
267
- (urljoin(api_url, "templates", template_action_regex, "?"), ManageTemplatesHandler),
268
- (urljoin(api_url, "templates", "variables"), TemplateVariableHandler),
269
127
  (urljoin(api_url, "kernelspec"), KernelSpecHandler),
270
- (urljoin(api_url, "generate_worksheet"), GenerateExerciseHandler),
271
128
  (urljoin(api_url, "git", "author"), GitAuthorHandler),
272
129
  ]
@@ -1,41 +1,102 @@
1
- import json
1
+ import inspect
2
+ from typing import List
2
3
 
3
4
  from e2xcore.handlers import E2xApiHandler
4
5
  from nbgrader.server_extensions.formgrader.base import check_xsrf
5
6
  from tornado import web
6
7
 
8
+ from e2xauthoring.managers.dataclasses import ErrorMessage, SuccessMessage
7
9
 
8
- class BaseApiManageHandler(E2xApiHandler):
9
- def initialize(self, model_cls):
10
- self.__model = model_cls(self.coursedir)
11
10
 
12
- @web.authenticated
13
- @check_xsrf
14
- def post(self, **kwargs):
15
- self.write(self.__model.new(**kwargs))
11
+ def status_msg(method):
12
+ """Decorates a method of a BaseApiManager
13
+ Wraps the response in a status message (ErrorMessage or SuccessMessage)
14
+ and catches exceptions.
15
+
16
+ Args:
17
+ method: The method to decorate
18
+ """
19
+
20
+ def wrapper(*args, **kwargs):
21
+ try:
22
+ res = method(*args, **kwargs)
23
+ return SuccessMessage(data=res)
24
+ except Exception as e:
25
+ return ErrorMessage(error=getattr(e, "message", str(e)))
26
+
27
+ return wrapper
28
+
29
+
30
+ class ApiManageHandler(E2xApiHandler):
31
+ def initialize(self, manager_cls, actions) -> None:
32
+ self.__manager = manager_cls(self.coursedir)
33
+ self.__allowed_actions = {
34
+ request_type: dict(default=None, actions=[])
35
+ for request_type in ["delete", "get", "post", "put"]
36
+ }
37
+ self.set_allowed_actions(actions)
38
+
39
+ def set_allowed_actions(self, allowed_actions):
40
+ for request_type, action_dict in self.__allowed_actions.items():
41
+ if request_type in allowed_actions:
42
+ actions = allowed_actions[request_type].get("actions", [])
43
+ for action in actions:
44
+ assert hasattr(
45
+ self.__manager, action
46
+ ), f"The manager class does not provide an action called {action}."
47
+ action_dict["default"] = allowed_actions[request_type].get(
48
+ "default", None
49
+ )
50
+ action_dict["actions"].extend(actions)
51
+
52
+ def extract_arguments(self, method: str):
53
+ params = dict()
54
+ json_body = self.get_json_body() or dict()
55
+ for arg in inspect.signature(method).parameters.keys():
56
+ argument = self.get_argument(name=arg, default=None)
57
+ if argument is None:
58
+ argument = json_body.get(arg, None)
59
+ params[arg] = argument
60
+ return params
61
+
62
+ def perform_action(self, action: str, allowed_actions: List[str]):
63
+ assert (
64
+ action is not None and action in allowed_actions
65
+ ), f"Action {action} is not a valid action."
66
+ method = getattr(self.__manager, action)
67
+ arguments = self.extract_arguments(method)
68
+ return method(**arguments)
69
+
70
+ @status_msg
71
+ def handle_request(self, request_type: str):
72
+ action = self.get_argument(
73
+ "action", default=None # self.__allowed_actions[request_type]["default"]
74
+ )
75
+ if action is None:
76
+ action = (self.get_json_body() or dict()).get(
77
+ "action", self.__allowed_actions[request_type]["default"]
78
+ )
79
+
80
+ return self.perform_action(
81
+ action, self.__allowed_actions[request_type]["actions"]
82
+ )
16
83
 
17
84
  @web.authenticated
18
85
  @check_xsrf
19
- def delete(self, **kwargs):
20
- self.__model.remove(**kwargs)
21
- self.write({"status": True})
86
+ def get(self):
87
+ self.finish(self.handle_request("get").json())
22
88
 
23
89
  @web.authenticated
24
90
  @check_xsrf
25
- def get(self, **kwargs):
26
- self.write(json.dumps(self.__model.get(**kwargs)))
91
+ def delete(self):
92
+ self.finish(self.handle_request("delete").json())
27
93
 
28
94
  @web.authenticated
29
95
  @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)
96
+ def put(self):
97
+ self.finish(self.handle_request("put").json())
37
98
 
38
99
  @web.authenticated
39
100
  @check_xsrf
40
- def get(self, **kwargs):
41
- self.write(json.dumps(self.__model.list(**kwargs)))
101
+ def post(self):
102
+ self.finish(self.handle_request("post").json())
@@ -1,10 +1,10 @@
1
1
  {
2
2
  "files": {
3
- "main.js": "/e2x/authoring/static/js/main.fe9bcd55.js",
3
+ "main.js": "/e2x/authoring/static/js/main.1e6f65c1.js",
4
4
  "index.html": "/e2x/authoring/index.html",
5
- "main.fe9bcd55.js.map": "/e2x/authoring/static/js/main.fe9bcd55.js.map"
5
+ "main.1e6f65c1.js.map": "/e2x/authoring/static/js/main.1e6f65c1.js.map"
6
6
  },
7
7
  "entrypoints": [
8
- "static/js/main.fe9bcd55.js"
8
+ "static/js/main.1e6f65c1.js"
9
9
  ]
10
10
  }
@@ -1 +1 @@
1
- <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>e²xgrader - Authoring</title><script type="text/javascript">window.base_url="{{ base_url }}",window.url_prefix="{{ url_prefix }}",window.webpackstuff="/e2x/authoring/static/js/main.fe9bcd55.js"</script><script defer="defer" src="{{base_url}}/e2x/authoring/static/js/main.fe9bcd55.js"></script>{%- block head -%} {%- endblock -%}</head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>
1
+ <!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>e²xgrader - Authoring</title><script type="text/javascript">window.base_url="{{ base_url }}",window.url_prefix="{{ url_prefix }}",window.webpackstuff="/e2x/authoring/static/js/main.1e6f65c1.js"</script><script defer="defer" src="{{base_url}}/e2x/authoring/static/js/main.1e6f65c1.js"></script>{%- block head -%} {%- endblock -%}</head><body><noscript>You need to enable JavaScript to run this app.</noscript><div id="root"></div></body></html>