Commit fc5333764a027620e21a40f75ed858b04cc9c10d

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

Fix pylint warnings, mostly

there is still more work to do...
aprendizations/initdb.py
... ... @@ -84,25 +84,24 @@ def get_students_from_csv(filename):
84 84 'skipinitialspace': True,
85 85 }
86 86  
  87 + students = []
87 88 try:
88 89 with open(filename, encoding='iso-8859-1') as file:
89 90 csvreader = csv.DictReader(file, **csv_settings)
90 91 students = [{
91 92 'uid': s['N.º'],
92 93 'name': capwords(re.sub(r'\(.*\)', '', s['Nome']).strip())
93   - } for s in csvreader]
  94 + } for s in csvreader]
94 95 except OSError:
95 96 print(f'!!! Error reading file "{filename}" !!!')
96   - students = []
97 97 except csv.Error:
98 98 print(f'!!! Error parsing CSV from "{filename}" !!!')
99   - students = []
100 99  
101 100 return students
102 101  
103 102  
104 103 # ===========================================================================
105   -def show_students_in_database(session, verbose=False):
  104 +def show_students_in_database(session, verbose=False) -> None:
106 105 '''shows students in the database'''
107 106 users = session.execute(select(Student)).scalars().all()
108 107 total = len(users)
... ... @@ -136,7 +135,7 @@ def main():
136 135 Base.metadata.create_all(engine) # Creates schema if needed
137 136 session = Session(engine, future=True)
138 137  
139   - # --- build list of students to insert/update
  138 + # --- make list of students to insert/update
140 139 students = []
141 140  
142 141 for csvfile in args.csvfile:
... ... @@ -145,24 +144,22 @@ def main():
145 144 if args.admin:
146 145 students.append({'uid': '0', 'name': 'Admin'})
147 146  
148   - if args.add:
  147 + if args.add is not None:
149 148 for uid, name in args.add:
150 149 students.append({'uid': uid, 'name': name})
151 150  
152 151 # --- only insert students that are not yet in the database
153 152 print('\nInserting new students:')
  153 + db_students = session.execute(select(Student.id)).scalars().all()
  154 + new_students = [s for s in students if s['uid'] not in set(db_students)]
  155 + for student in new_students:
  156 + print(f' {student["uid"]}, {student["name"]}')
154 157  
155   - db_students = set(session.execute(select(Student.id)).scalars().all())
156   - new_students = (s for s in students if s['uid'] not in db_students)
157   - count = 0
158   - for s in new_students:
159   - print(f' {s["uid"]}, {s["name"]}')
160   -
161   - pw = args.pw or s['uid']
162   - hashed_pw = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
163   -
164   - session.add(Student(id=s['uid'], name=s['name'], password=hashed_pw))
165   - count += 1
  158 + passwd = args.pw or student['uid']
  159 + hashed_pw = bcrypt.hashpw(passwd.encode('utf-8'), bcrypt.gensalt())
  160 + session.add(Student(id=student['uid'],
  161 + name=student['name'],
  162 + password=hashed_pw))
166 163  
167 164 try:
168 165 session.commit()
... ... @@ -170,23 +167,24 @@ def main():
170 167 print('!!! Integrity error. Aborted !!!\n')
171 168 session.rollback()
172 169 else:
173   - print(f'Total {count} new student(s).')
  170 + print(f'Total {len(new_students)} new student(s).')
174 171  
175   - # --- update data for student in the database
  172 + # --- update data for students in the database
176 173 if args.update:
177 174 print('\nUpdating passwords of students:')
178 175 count = 0
179 176 for sid in args.update:
180 177 try:
181   - s = session.execute(select(Student).filter_by(id=sid)).scalar_one()
  178 + query = select(Student).filter_by(id=sid)
  179 + student = session.execute(query).scalar_one()
182 180 except NoResultFound:
183 181 print(f' -> student {sid} does not exist!')
184 182 continue
185   - else:
186   - print(f' {sid}, {s.name}')
187   - count += 1
188   - pw = args.pw or sid
189   - s.password = bcrypt.hashpw(pw.encode('utf-8'), bcrypt.gensalt())
  183 + count += 1
  184 + print(f' {sid}, {student.name}')
  185 + passwd = (args.pw or sid).encode('utf-8')
  186 + student.password = bcrypt.hashpw(passwd, bcrypt.gensalt())
  187 +
190 188 session.commit()
191 189 print(f'Total {count} password(s) updated.')
192 190  
... ...
aprendizations/learnapp.py
... ... @@ -66,11 +66,11 @@ class LearnApp():
66 66 def __init__(self,
67 67 courses: str, # filename with course configurations
68 68 prefix: str, # path to topics
69   - db: str, # database filename
  69 + dbase: str, # database filename
70 70 check: bool = False) -> None:
71 71  
72   - self._db_setup(db) # setup database and check students
73   - self.online: Dict[str, Dict] = dict() # online students
  72 + self._db_setup(dbase) # setup database and check students
  73 + self.online: Dict[str, Dict] = {} # online students
74 74  
75 75 try:
76 76 config: Dict[str, Any] = load_yaml(courses)
... ... @@ -91,7 +91,6 @@ class LearnApp():
91 91 # load other course files with the topics the their deps
92 92 for course_file in config.get('topics_from', []):
93 93 course_conf = load_yaml(course_file) # course configuration
94   - # FIXME set defaults??
95 94 logger.info('%6d topics from %s',
96 95 len(course_conf["topics"]), course_file)
97 96 self._populate_graph(course_conf)
... ... @@ -129,10 +128,10 @@ class LearnApp():
129 128 logger.info('Starting sanity checks (may take a while...)')
130 129  
131 130 errors: int = 0
132   - for qref in self.factory:
  131 + for qref, qfactory in self.factory.items():
133 132 logger.debug('checking %s...', qref)
134 133 try:
135   - question = self.factory[qref].generate()
  134 + question = qfactory.generate()
136 135 except QuestionException as exc:
137 136 logger.error(exc)
138 137 errors += 1
... ... @@ -334,9 +333,6 @@ class LearnApp():
334 333 '''Start new topic'''
335 334  
336 335 student = self.online[uid]['state']
337   - # if uid == '0':
338   - # logger.warning('Reloading "%s"', topic) # FIXME should be an option
339   - # self.factory.update(self._factory_for(topic))
340 336  
341 337 try:
342 338 await student.start_topic(topic)
... ... @@ -420,7 +416,7 @@ class LearnApp():
420 416  
421 417 topic = self.deps.nodes[tref] # get current topic node
422 418 topic['name'] = attr.get('name', tref)
423   - topic['questions'] = attr.get('questions', []) # FIXME unused??
  419 + topic['questions'] = attr.get('questions', [])
424 420  
425 421 for k, default in defaults.items():
426 422 topic[k] = attr.get(k, default)
... ... @@ -440,7 +436,7 @@ class LearnApp():
440 436 '''
441 437  
442 438 logger.info('Building questions factory:')
443   - factory = dict()
  439 + factory = {}
444 440 for tref in self.deps.nodes:
445 441 factory.update(self._factory_for(tref))
446 442  
... ... @@ -451,7 +447,7 @@ class LearnApp():
451 447 # makes factory for a single topic
452 448 # ------------------------------------------------------------------------
453 449 def _factory_for(self, tref: str) -> Dict[str, QFactory]:
454   - factory: Dict[str, QFactory] = dict()
  450 + factory: Dict[str, QFactory] = {}
455 451 topic = self.deps.nodes[tref] # get node
456 452 # load questions as list of dicts
457 453 try:
... ... @@ -509,7 +505,7 @@ class LearnApp():
509 505 return factory
510 506  
511 507 def get_login_counter(self, uid: str) -> int:
512   - '''login counter''' # FIXME
  508 + '''login counter'''
513 509 return int(self.online[uid]['counter'])
514 510  
515 511 def get_student_name(self, uid: str) -> str:
... ... @@ -602,14 +598,16 @@ class LearnApp():
602 598 student_topics = session.execute(query_student_topics).all()
603 599  
604 600 # compute topic progress
605   - now = datetime.now()
606   - goals = self.courses[cid]['goals']
607 601 progress: DefaultDict[str, float] = defaultdict(int)
  602 + goals = self.courses[cid]['goals']
  603 + num_goals = len(goals)
  604 + now = datetime.now()
608 605  
609 606 for student, topic, level, datestr in student_topics:
610 607 if topic in goals:
611 608 date = datetime.strptime(datestr, "%Y-%m-%d %H:%M:%S.%f")
612   - progress[student] += level**(now - date).days / len(goals)
  609 + elapsed_days = (now - date).days
  610 + progress[student] += level**elapsed_days / num_goals
613 611  
614 612 return sorted(((u, name, progress[u])
615 613 for u, name in students
... ...
aprendizations/main.py
... ... @@ -182,7 +182,7 @@ def main():
182 182 try:
183 183 learnapp = LearnApp(courses=arg.courses,
184 184 prefix=arg.prefix,
185   - db=arg.db,
  185 + dbase=arg.db,
186 186 check=arg.check)
187 187 except DatabaseUnusableError:
188 188 logging.critical('Failed to start application.')
... ... @@ -197,10 +197,7 @@ def main():
197 197 sep='\n')
198 198 sys.exit(1)
199 199 except LearnException as exc:
200   - logging.critical('Failed to start backend')
201   - sys.exit(1)
202   - except Exception:
203   - logging.critical('Unknown error')
  200 + logging.critical('Failed to start backend: %s', str(exc))
204 201 sys.exit(1)
205 202 else:
206 203 logging.info('LearnApp started')
... ...
aprendizations/serve.py
... ... @@ -4,10 +4,9 @@ Webserver
4 4  
5 5  
6 6 # python standard library
7   -import asyncio
8 7 import base64
9 8 import functools
10   -import logging
  9 +from logging import getLogger
11 10 import mimetypes
12 11 from os.path import join, dirname, expanduser
13 12 import signal
... ... @@ -28,7 +27,7 @@ from aprendizations import APP_NAME
28 27  
29 28  
30 29 # setup logger for this module
31   -logger = logging.getLogger(__name__)
  30 +logger = getLogger(__name__)
32 31  
33 32  
34 33 # ----------------------------------------------------------------------------
... ... @@ -37,7 +36,7 @@ def admin_only(func):
37 36 Decorator used to restrict access to the administrator
38 37 '''
39 38 @functools.wraps(func)
40   - def wrapper(self, *args, **kwargs):
  39 + def wrapper(self, *args, **kwargs) -> None:
41 40 if self.current_user != '0':
42 41 raise tornado.web.HTTPError(403) # forbidden
43 42 func(self, *args, **kwargs)
... ... @@ -49,7 +48,7 @@ class WebApplication(tornado.web.Application):
49 48 '''
50 49 WebApplication - Tornado Web Server
51 50 '''
52   - def __init__(self, learnapp, debug=False):
  51 + def __init__(self, learnapp, debug=False) -> None:
53 52 handlers = [
54 53 (r'/login', LoginHandler),
55 54 (r'/logout', LogoutHandler),
... ... @@ -94,7 +93,6 @@ class BaseHandler(tornado.web.RequestHandler):
94 93 counter_cookie = self.get_secure_cookie('counter')
95 94 if user_cookie is not None:
96 95 uid = user_cookie.decode('utf-8')
97   -
98 96 if counter_cookie is not None:
99 97 counter = counter_cookie.decode('utf-8')
100 98 if counter == str(self.learn.get_login_counter(uid)):
... ... @@ -108,7 +106,7 @@ class RankingsHandler(BaseHandler):
108 106 Handles rankings page
109 107 '''
110 108 @tornado.web.authenticated
111   - def get(self):
  109 + def get(self) -> None:
112 110 '''
113 111 Renders list of students that have answers in this course.
114 112 '''
... ... @@ -122,18 +120,17 @@ class RankingsHandler(BaseHandler):
122 120 name=self.learn.get_student_name(uid),
123 121 rankings=rankings,
124 122 course_id=course_id,
125   - course_title=self.learn.get_student_course_title(uid), # FIXME get from course var
  123 + course_title=self.learn.get_student_course_title(uid),
  124 + # FIXME get from course var
126 125 )
127 126  
128 127  
129 128 # ----------------------------------------------------------------------------
130   -#
131   -# ----------------------------------------------------------------------------
132 129 class LoginHandler(BaseHandler):
133 130 '''
134 131 Handles /login
135 132 '''
136   - def get(self):
  133 + def get(self) -> None:
137 134 '''
138 135 Renders login page
139 136 '''
... ... @@ -168,7 +165,7 @@ class LogoutHandler(BaseHandler):
168 165 Handles /logout
169 166 '''
170 167 @tornado.web.authenticated
171   - def get(self):
  168 + def get(self) -> None:
172 169 '''
173 170 clears cookies and removes user session
174 171 '''
... ... @@ -176,7 +173,7 @@ class LogoutHandler(BaseHandler):
176 173 self.clear_cookie('counter')
177 174 self.redirect('/')
178 175  
179   - def on_finish(self):
  176 + def on_finish(self) -> None:
180 177 self.learn.logout(self.current_user)
181 178  
182 179  
... ... @@ -186,7 +183,7 @@ class ChangePasswordHandler(BaseHandler):
186 183 Handles password change
187 184 '''
188 185 @tornado.web.authenticated
189   - async def post(self):
  186 + async def post(self) -> None:
190 187 '''
191 188 Tries to perform password change and then replies success/fail status
192 189 '''
... ... @@ -216,7 +213,7 @@ class RootHandler(BaseHandler):
216 213 Handles root /
217 214 '''
218 215 @tornado.web.authenticated
219   - def get(self):
  216 + def get(self) -> None:
220 217 '''Simply redirects to the main entrypoint'''
221 218 self.redirect('/courses')
222 219  
... ... @@ -226,11 +223,11 @@ class CoursesHandler(BaseHandler):
226 223 '''
227 224 Handles /courses
228 225 '''
229   - def set_default_headers(self, *args, **kwargs):
  226 + def set_default_headers(self, *_) -> None:
230 227 self.set_header('Cache-Control', 'no-cache')
231 228  
232 229 @tornado.web.authenticated
233   - def get(self):
  230 + def get(self) -> None:
234 231 '''Renders list of available courses'''
235 232 uid = self.current_user
236 233 self.render('courses.html',
... ... @@ -249,7 +246,7 @@ class CourseHandler(BaseHandler):
249 246 '''
250 247  
251 248 @tornado.web.authenticated
252   - def get(self, course_id):
  249 + def get(self, course_id) -> None:
253 250 '''
254 251 Handles get /course/...
255 252 Starts a given course and show list of topics
... ... @@ -278,11 +275,11 @@ class TopicHandler(BaseHandler):
278 275 '''
279 276 Handles a topic
280 277 '''
281   - def set_default_headers(self, *args, **kwargs):
  278 + def set_default_headers(self, *_) -> None:
282 279 self.set_header('Cache-Control', 'no-cache')
283 280  
284 281 @tornado.web.authenticated
285   - async def get(self, topic):
  282 + async def get(self, topic) -> None:
286 283 '''
287 284 Handles get /topic/...
288 285 Starts a given topic
... ... @@ -308,7 +305,7 @@ class FileHandler(BaseHandler):
308 305 Serves files from the /public subdir of the topics.
309 306 '''
310 307 @tornado.web.authenticated
311   - async def get(self, filename):
  308 + async def get(self, filename) -> None:
312 309 '''
313 310 Serves files from /public subdirectories of a particular topic
314 311 '''
... ... @@ -351,7 +348,7 @@ class QuestionHandler(BaseHandler):
351 348  
352 349 # ------------------------------------------------------------------------
353 350 @tornado.web.authenticated
354   - async def get(self):
  351 + async def get(self) -> None:
355 352 '''
356 353 Gets question to render.
357 354 Shows an animated trophy if there are no more questions in the topic.
... ... @@ -489,7 +486,7 @@ def signal_handler(*_) -> None:
489 486 reply = input(' --> Stop webserver? (yes/no) ')
490 487 if reply.lower() == 'yes':
491 488 tornado.ioloop.IOLoop.current().stop()
492   - logging.critical('Webserver stopped.')
  489 + logger.critical('Webserver stopped.')
493 490 sys.exit(0)
494 491  
495 492  
... ... @@ -503,10 +500,9 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None:
503 500 try:
504 501 webapp = WebApplication(app, debug=debug)
505 502 except Exception:
506   - logger.critical('Failed to start web application.')
  503 + logger.critical('Failed to start web application.', exc_info=True)
507 504 sys.exit(1)
508   - else:
509   - logger.info('Web application started (tornado.web.Application)')
  505 + logger.info('Web application started (tornado.web.Application)')
510 506  
511 507 # --- create tornado webserver
512 508 try:
... ... @@ -514,19 +510,17 @@ def run_webserver(app, ssl, port: int = 8443, debug: bool = False) -> None:
514 510 except ValueError:
515 511 logger.critical('Certificates cert.pem and privkey.pem not found')
516 512 sys.exit(1)
517   - else:
518   - logger.debug('HTTP server started')
  513 + logger.debug('HTTP server started')
519 514  
520 515 try:
521 516 httpserver.listen(port)
522 517 except OSError:
523 518 logger.critical('Cannot bind port %d. Already in use?', port)
524 519 sys.exit(1)
525   -
526   - # --- run webserver
527 520 logger.info('Webserver listening on %d... (Ctrl-C to stop)', port)
528 521 signal.signal(signal.SIGINT, signal_handler)
529 522  
  523 + # --- run webserver
530 524 try:
531 525 tornado.ioloop.IOLoop.current().start() # running...
532 526 except Exception:
... ...
setup.py
... ... @@ -18,7 +18,7 @@ setup(
18 18 url="https://git.xdi.uevora.pt/mjsb/aprendizations.git",
19 19 packages=find_packages(),
20 20 include_package_data=True, # install files from MANIFEST.in
21   - python_requires='>=3.8.*',
  21 + python_requires='>=3.9.*',
22 22 install_requires=[
23 23 'tornado>=6.0',
24 24 'mistune<2',
... ...