Commit fa091c84af26c006bf1964d0972a737d737c23ee
1 parent
8e601953
Exists in
master
and in
1 other branch
Allow generator to generate arguments to correction script of textarea.
Arguments are no longer sent to stdin, but in the command line instead. Topic sequence shows all the unlocked topics first. Drop compatibility with python 3.6 because of asyncio.get_running_loop().
Showing
8 changed files
with
30 additions
and
39 deletions
Show diff stats
BUGS.md
... | ... | @@ -10,6 +10,7 @@ |
10 | 10 | |
11 | 11 | # TODO |
12 | 12 | |
13 | +- ao fim de 3 tentativas de login, envial email para aluno com link para definir nova password (com timeout de 5 minutos). | |
13 | 14 | - mostrar capitulos e subtopicos de forma hierarquica. clicar no capitulo expande as dependencias. |
14 | 15 | - mostrar rankings alunos/perguntas respondidas/% correctas/valor esperado topicos. |
15 | 16 | - botão não sei... | ... | ... |
aprendizations/factory.py
... | ... | @@ -83,9 +83,10 @@ class QFactory(object): |
83 | 83 | # output is then yaml parsed into a dictionary `q`. |
84 | 84 | if q['type'] == 'generator': |
85 | 85 | logger.debug(f' \\_ Running "{q["script"]}".') |
86 | - q.setdefault('arg', '') # optional arguments will be sent to stdin | |
86 | + q.setdefault('args', []) | |
87 | + q.setdefault('stdin', '') | |
87 | 88 | script = path.join(q['path'], q['script']) |
88 | - out = run_script(script=script, stdin=q['arg']) | |
89 | + out = run_script(script=script, args=q['args'], stdin=q['stdin']) | |
89 | 90 | q.update(out) |
90 | 91 | |
91 | 92 | # Finally we create an instance of Question() | ... | ... |
aprendizations/knowledge.py
... | ... | @@ -31,8 +31,8 @@ class StudentKnowledge(object): |
31 | 31 | self.state = state # {'topic': {'level': 0.5, 'date': datetime}, ...} |
32 | 32 | |
33 | 33 | self.update_topic_levels() # applies forgetting factor |
34 | - self.topic_sequence = self.recommend_topic_sequence() # ['ref1', ...] | |
35 | 34 | self.unlock_topics() # whose dependencies have been completed |
35 | + self.topic_sequence = self.recommend_topic_sequence() # ['ref1', ...] | |
36 | 36 | self.current_topic = None |
37 | 37 | |
38 | 38 | # ------------------------------------------------------------------------ |
... | ... | @@ -53,7 +53,7 @@ class StudentKnowledge(object): |
53 | 53 | # for the topic to be unlocked. |
54 | 54 | min_level = 0.01 |
55 | 55 | |
56 | - for topic in self.topic_sequence: | |
56 | + for topic in self.deps.nodes(): | |
57 | 57 | if topic not in self.state: # if locked |
58 | 58 | pred = self.deps.predecessors(topic) |
59 | 59 | if all(d in self.state and self.state[d]['level'] > min_level |
... | ... | @@ -185,19 +185,10 @@ class StudentKnowledge(object): |
185 | 185 | # compute recommended sequence of topics ['a', 'b', ...] |
186 | 186 | # ------------------------------------------------------------------------ |
187 | 187 | def recommend_topic_sequence(self, target=None): |
188 | - return list(nx.topological_sort(self.deps)) | |
189 | - | |
190 | - # if target is None: | |
191 | - # target = list(nx.topological_sort(self.deps))[-1] | |
192 | - | |
193 | - # weights = {} | |
194 | - # tseq = [target] | |
195 | - | |
196 | - # def topic_weights(d, g, n): | |
197 | - # pred = g.predecessors(n) | |
198 | - # for p in pred: | |
199 | - # topic_weights(d, g, p) | |
200 | - # d[n] = 1 + sum(d[m]) | |
188 | + tt = list(nx.topological_sort(self.deps)) | |
189 | + unlocked = [t for t in tt if t in self.state] | |
190 | + locked = [t for t in tt if t not in unlocked] | |
191 | + return unlocked + locked | |
201 | 192 | |
202 | 193 | # ------------------------------------------------------------------------ |
203 | 194 | def get_current_question(self): | ... | ... |
aprendizations/questions.py
... | ... | @@ -49,10 +49,9 @@ class Question(dict): |
49 | 49 | return 0.0 |
50 | 50 | |
51 | 51 | async def correct_async(self) -> float: |
52 | - # loop = asyncio.get_running_loop() # FIXME python 3.7 only | |
53 | - loop = asyncio.get_event_loop() | |
54 | - grade = await loop.run_in_executor(None, self.correct) | |
55 | - return grade | |
52 | + loop = asyncio.get_running_loop() # FIXME python 3.7 only | |
53 | + # loop = asyncio.get_event_loop() # python 3.6 | |
54 | + return await loop.run_in_executor(None, self.correct) | |
56 | 55 | |
57 | 56 | def set_defaults(self, d): |
58 | 57 | 'Add k:v pairs from default dict d for nonexistent keys' |
... | ... | @@ -234,7 +233,6 @@ class QuestionText(Question): |
234 | 233 | self['correct'] = [str(a) for a in self['correct']] |
235 | 234 | |
236 | 235 | # ------------------------------------------------------------------------ |
237 | - # can return negative values for wrong answers | |
238 | 236 | def correct(self): |
239 | 237 | super().correct() |
240 | 238 | |
... | ... | @@ -263,7 +261,6 @@ class QuestionTextRegex(Question): |
263 | 261 | }) |
264 | 262 | |
265 | 263 | # ------------------------------------------------------------------------ |
266 | - # can return negative values for wrong answers | |
267 | 264 | def correct(self): |
268 | 265 | super().correct() |
269 | 266 | if self['answer'] is not None: |
... | ... | @@ -297,7 +294,6 @@ class QuestionNumericInterval(Question): |
297 | 294 | }) |
298 | 295 | |
299 | 296 | # ------------------------------------------------------------------------ |
300 | - # can return negative values for wrong answers | |
301 | 297 | def correct(self): |
302 | 298 | super().correct() |
303 | 299 | if self['answer'] is not None: |
... | ... | @@ -333,20 +329,20 @@ class QuestionTextArea(Question): |
333 | 329 | 'text': '', |
334 | 330 | 'lines': 8, |
335 | 331 | 'timeout': 5, # seconds |
336 | - 'correct': '' # trying to execute this will fail => grade 0.0 | |
332 | + 'correct': '', # trying to execute this will fail => grade 0.0 | |
333 | + 'args': [] | |
337 | 334 | }) |
338 | 335 | |
339 | 336 | self['correct'] = path.join(self['path'], self['correct']) # FIXME |
340 | 337 | |
341 | 338 | # ------------------------------------------------------------------------ |
342 | - # can return negative values for wrong answers | |
343 | 339 | def correct(self): |
344 | 340 | super().correct() |
345 | 341 | |
346 | - if self['answer'] is not None: | |
347 | - # correct answer | |
348 | - out = run_script( # and parse yaml ouput | |
342 | + if self['answer'] is not None: # correct answer and parse yaml ouput | |
343 | + out = run_script( | |
349 | 344 | script=self['correct'], |
345 | + args=self['args'], | |
350 | 346 | stdin=self['answer'], |
351 | 347 | timeout=self['timeout'] |
352 | 348 | ) | ... | ... |
aprendizations/templates/maintopics-table.html
... | ... | @@ -102,6 +102,7 @@ |
102 | 102 | {% elif t['type']=='chapter' %} |
103 | 103 | <div class="text-xs-right"> |
104 | 104 | <i class="fas fa-trophy fa-lg text-warning"></i> |
105 | + <!-- <i class="fas fa-award fa-lg text-warning"></i> --> | |
105 | 106 | </div> |
106 | 107 | {% else %} |
107 | 108 | <div class="text-xs-right"> | ... | ... |
aprendizations/tools.py
... | ... | @@ -161,10 +161,11 @@ def load_yaml(filename, default=None): |
161 | 161 | # The script is run in another process but this function blocks waiting |
162 | 162 | # for its termination. |
163 | 163 | # --------------------------------------------------------------------------- |
164 | -def run_script(script, stdin='', timeout=5): | |
164 | +def run_script(script, args=[], stdin='', timeout=5): | |
165 | 165 | script = path.expanduser(script) |
166 | 166 | try: |
167 | - p = subprocess.run([script], | |
167 | + cmd = [script] + [str(a) for a in args] | |
168 | + p = subprocess.run(cmd, | |
168 | 169 | input=stdin, |
169 | 170 | stdout=subprocess.PIPE, |
170 | 171 | stderr=subprocess.STDOUT, |
... | ... | @@ -179,6 +180,8 @@ def run_script(script, stdin='', timeout=5): |
179 | 180 | logger.error(f'Can not execute script "{script}": unknown reason.') |
180 | 181 | except subprocess.TimeoutExpired: |
181 | 182 | logger.error(f'Timeout {timeout}s exceeded while running "{script}".') |
183 | + except Exception: | |
184 | + logger.error(f'An Exception ocurred running {script}.') | |
182 | 185 | else: |
183 | 186 | if p.returncode != 0: |
184 | 187 | logger.error(f'Return code {p.returncode} running "{script}".') | ... | ... |
demo/math/gen-multiples-of-3.py
... | ... | @@ -3,9 +3,8 @@ |
3 | 3 | import random |
4 | 4 | import sys |
5 | 5 | |
6 | -arg = sys.stdin.read() # read arguments | |
6 | +a, b = map(int, sys.argv[1:]) # get command line arguments | |
7 | 7 | |
8 | -a,b = (int(n) for n in arg.split(',')) | |
9 | 8 | numbers = list(range(a, b)) |
10 | 9 | random.shuffle(numbers) |
11 | 10 | numbers = numbers[:8] |
... | ... | @@ -18,14 +17,13 @@ else: |
18 | 17 | solution = f'Nenhum número mostrado é múltiplo de 3.' |
19 | 18 | |
20 | 19 | |
21 | -q = f''' | |
20 | +q = f'''--- | |
22 | 21 | type: checkbox |
23 | 22 | title: Múltiplos de 3 |
24 | 23 | text: Indique quais dos seguintes números são múltiplos de 3. |
25 | 24 | options: {numbers} |
26 | 25 | correct: {correct} |
27 | 26 | solution: | |
28 | - {solution} | |
29 | -''' | |
27 | + {solution}''' | |
30 | 28 | |
31 | 29 | print(q) | ... | ... |
demo/math/questions.yaml
... | ... | @@ -79,6 +79,6 @@ |
79 | 79 | - type: generator |
80 | 80 | ref: overflow |
81 | 81 | script: gen-multiples-of-3.py |
82 | - arg: "11,120" | |
83 | - # the program should print a question in yaml format. The arg will be | |
84 | - # sent to the stdin when the program is run. | |
82 | + args: [11, 120] | |
83 | + # the program should print a question in yaml format. The args will be | |
84 | + # sent as command line options when the program is run. | ... | ... |