Commit fa091c84af26c006bf1964d0972a737d737c23ee

Authored by Miguel Barão
1 parent 8e601953
Exists in master and in 1 other branch dev

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().
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=&#39;&#39;, 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.
... ...