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