Commit 50d0cebf540a3a3cd8c6e4f612d078bec3c122c3

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

Upgrade to new sqlalchemy 1.4/2.0

- models.py -> done
- initdb.py -> done
aprendizations/initdb.py
@@ -9,13 +9,12 @@ import csv @@ -9,13 +9,12 @@ import csv
9 import argparse 9 import argparse
10 import re 10 import re
11 from string import capwords 11 from string import capwords
12 -from concurrent.futures import ThreadPoolExecutor  
13 12
14 # third party libraries 13 # third party libraries
15 import bcrypt 14 import bcrypt
16 -import sqlalchemy as sa  
17 -import sqlalchemy.orm as orm  
18 -from sqlalchemy.exc import IntegrityError 15 +from sqlalchemy import create_engine, select
  16 +from sqlalchemy.orm import Session
  17 +from sqlalchemy.exc import IntegrityError, NoResultFound
19 18
20 # this project 19 # this project
21 from aprendizations.models import Base, Student 20 from aprendizations.models import Base, Student
@@ -103,24 +102,14 @@ def get_students_from_csv(filename): @@ -103,24 +102,14 @@ def get_students_from_csv(filename):
103 102
104 103
105 # =========================================================================== 104 # ===========================================================================
106 -def hashpw(student, passw=None):  
107 - '''replace password by hash for a single student'''  
108 - print('.', end='', flush=True)  
109 - passw = (passw or student.get('pw', None) or student['uid']).encode('utf-8')  
110 - student['pw'] = bcrypt.hashpw(passw, bcrypt.gensalt())  
111 -  
112 -  
113 -# ===========================================================================  
114 def show_students_in_database(session, verbose=False): 105 def show_students_in_database(session, verbose=False):
115 - '''print students that are in the database'''  
116 - users = session.query(Student).all() 106 + '''shows students in the database'''
  107 + users = session.execute(select(Student)).scalars().all()
117 total = len(users) 108 total = len(users)
118 109
119 print('\nRegistered users:') 110 print('\nRegistered users:')
120 - if total == 0:  
121 - print(' -- none --')  
122 - else:  
123 - users.sort(key=lambda u: f'{u.id:>12}') # sort by number 111 + if users:
  112 + users.sort(key=lambda u: f'{u.id:>12}') # sort by right aligned string
124 if verbose: 113 if verbose:
125 for user in users: 114 for user in users:
126 print(f'{user.id:>12} {user.name}') 115 print(f'{user.id:>12} {user.name}')
@@ -142,65 +131,64 @@ def main(): @@ -142,65 +131,64 @@ def main():
142 args = parse_commandline_arguments() 131 args = parse_commandline_arguments()
143 132
144 # --- database stuff 133 # --- database stuff
145 - print(f'Using database: {args.db}')  
146 - engine = sa.create_engine(f'sqlite:///{args.db}', echo=False) 134 + print(f'Database: {args.db}')
  135 + engine = create_engine(f'sqlite:///{args.db}', echo=False, future=True)
147 Base.metadata.create_all(engine) # Creates schema if needed 136 Base.metadata.create_all(engine) # Creates schema if needed
148 - session = orm.sessionmaker(bind=engine)() 137 + session = Session(engine)
149 138
150 # --- build list of students to insert/update 139 # --- build list of students to insert/update
151 students = [] 140 students = []
152 141
153 for csvfile in args.csvfile: 142 for csvfile in args.csvfile:
154 - # print('Adding users from:', csvfile)  
155 - students.extend(get_students_from_csv(csvfile)) 143 + students += get_students_from_csv(csvfile)
156 144
157 if args.admin: 145 if args.admin:
158 - # print('Adding user: 0, Admin.')  
159 students.append({'uid': '0', 'name': 'Admin'}) 146 students.append({'uid': '0', 'name': 'Admin'})
160 147
161 if args.add: 148 if args.add:
162 for uid, name in args.add: 149 for uid, name in args.add:
163 - # print(f'Adding user: {uid}, {name}.')  
164 students.append({'uid': uid, 'name': name}) 150 students.append({'uid': uid, 'name': name})
165 151
166 # --- only insert students that are not yet in the database 152 # --- only insert students that are not yet in the database
167 - db_students = {user.id for user in session.query(Student).all()}  
168 - new_students = list(filter(lambda s: s['uid'] not in db_students, students))  
169 -  
170 - if new_students:  
171 - # --- password hashing  
172 - print('Generating password hashes', end='')  
173 - with ThreadPoolExecutor() as executor:  
174 - executor.map(lambda s: hashpw(s, args.pw), new_students)  
175 -  
176 - print('\nAdding students:')  
177 - for student in new_students:  
178 - print(f' + {student["uid"]}, {student["name"]}')  
179 -  
180 - try:  
181 - session.add_all([Student(id=s['uid'],  
182 - name=s['name'],  
183 - password=s['pw'])  
184 - for s in new_students])  
185 - session.commit()  
186 - except IntegrityError:  
187 - print('!!! Integrity error. Aborted !!!\n')  
188 - session.rollback()  
189 -  
190 - print(f'Inserted {len(new_students)} new student(s).') 153 + print('\nInserting new students:')
  154 +
  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
  166 +
  167 + try:
  168 + session.commit()
  169 + except IntegrityError:
  170 + print('!!! Integrity error. Aborted !!!\n')
  171 + session.rollback()
191 else: 172 else:
192 - print('There are no new students to add.') 173 + print(f'Total {count} new student(s).')
193 174
194 # --- update data for student in the database 175 # --- update data for student in the database
195 - for student_id in args.update:  
196 - print(f'Updating password of: {student_id}')  
197 - student = session.query(Student).get(student_id)  
198 - if student is not None:  
199 - passw = (args.pw or student_id).encode('utf-8')  
200 - student.password = bcrypt.hashpw(passw, bcrypt.gensalt())  
201 - session.commit()  
202 - else:  
203 - print(f'!!! Student {student_id} does not exist. Skipped!!!') 176 + if args.update:
  177 + print('\nUpdating passwords of students:')
  178 + count = 0
  179 + for sid in args.update:
  180 + try:
  181 + s = session.execute(select(Student).filter_by(id=sid)).scalar_one()
  182 + except NoResultFound:
  183 + print(f' -> student {sid} does not exist!')
  184 + 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())
  190 + session.commit()
  191 + print(f'Total {count} password(s) updated.')
204 192
205 show_students_in_database(session, args.verbose) 193 show_students_in_database(session, args.verbose)
206 194
aprendizations/models.py
1 1
2 # python standard library 2 # python standard library
3 -from typing import Any 3 +# from typing import Any
4 4
5 # third party libraries 5 # third party libraries
6 from sqlalchemy import Column, ForeignKey, Integer, Float, String 6 from sqlalchemy import Column, ForeignKey, Integer, Float, String
7 -from sqlalchemy.ext.declarative import declarative_base  
8 -from sqlalchemy.orm import relationship 7 +from sqlalchemy.orm import declarative_base, relationship
9 8
10 9
11 # =========================================================================== 10 # ===========================================================================
12 # Declare ORM 11 # Declare ORM
13 # FIXME Any is a workaround for mypy static type checking (see https://github.com/python/mypy/issues/6372) 12 # FIXME Any is a workaround for mypy static type checking (see https://github.com/python/mypy/issues/6372)
14 -Base: Any = declarative_base() 13 +# Base: Any = declarative_base()
  14 +Base = declarative_base()
15 15
16 16
17 # --------------------------------------------------------------------------- 17 # ---------------------------------------------------------------------------
@@ -27,11 +27,11 @@ class StudentTopic(Base): @@ -27,11 +27,11 @@ class StudentTopic(Base):
27 topic = relationship('Topic', back_populates='students') 27 topic = relationship('Topic', back_populates='students')
28 28
29 def __repr__(self): 29 def __repr__(self):
30 - return f'''StudentTopic:  
31 - student_id: "{self.student_id}"  
32 - topic_id: "{self.topic_id}"  
33 - level: "{self.level}"  
34 - date: "{self.date}"''' 30 + return ('StudentTopic('
  31 + f'student_id={self.student_id!r}, '
  32 + f'topic_id={self.topic_id!r}, '
  33 + f'level={self.level!r}, '
  34 + f'date={self.date!r})')
35 35
36 36
37 # --------------------------------------------------------------------------- 37 # ---------------------------------------------------------------------------
@@ -48,10 +48,10 @@ class Student(Base): @@ -48,10 +48,10 @@ class Student(Base):
48 topics = relationship('StudentTopic', back_populates='student') 48 topics = relationship('StudentTopic', back_populates='student')
49 49
50 def __repr__(self): 50 def __repr__(self):
51 - return f'''Student:  
52 - id: "{self.id}"  
53 - name: "{self.name}"  
54 - password: "{self.password}"''' 51 + return ('Student('
  52 + f'id={self.id!r}, '
  53 + f'name={self.name!r}, '
  54 + f'password={self.password!r})')
55 55
56 56
57 # --------------------------------------------------------------------------- 57 # ---------------------------------------------------------------------------
@@ -72,14 +72,14 @@ class Answer(Base): @@ -72,14 +72,14 @@ class Answer(Base):
72 topic = relationship('Topic', back_populates='answers') 72 topic = relationship('Topic', back_populates='answers')
73 73
74 def __repr__(self): 74 def __repr__(self):
75 - return f'''Question:  
76 - id: "{self.id}"  
77 - ref: "{self.ref}"  
78 - grade: "{self.grade}"  
79 - starttime: "{self.starttime}"  
80 - finishtime: "{self.finishtime}"  
81 - student_id: "{self.student_id}"  
82 - topic_id: "{self.topic_id}"''' 75 + return ('Question('
  76 + f'id={self.id!r}, '
  77 + f'ref={self.ref!r}, '
  78 + f'grade={self.grade!r}, '
  79 + f'starttime={self.starttime!r}, '
  80 + f'finishtime={self.finishtime!r}, '
  81 + f'student_id={self.student_id!r}, '
  82 + f'topic_id={self.topic_id!r})')
83 83
84 84
85 # --------------------------------------------------------------------------- 85 # ---------------------------------------------------------------------------
@@ -94,5 +94,4 @@ class Topic(Base): @@ -94,5 +94,4 @@ class Topic(Base):
94 answers = relationship('Answer', back_populates='topic') 94 answers = relationship('Answer', back_populates='topic')
95 95
96 def __repr__(self): 96 def __repr__(self):
97 - return f'''Topic:  
98 - id: "{self.id}"''' 97 + return f'Topic(id={self.id!r})'
1 [mypy] 1 [mypy]
2 -python_version = 3.8  
3 -warn_return_any = True  
4 -warn_unused_configs = True 2 +python_version = 3.9
  3 +plugins = sqlalchemy.ext.mypy.plugin
5 4
6 -[mypy-sqlalchemy.*]  
7 -ignore_missing_imports = True  
8 5
9 [mypy-pygments.*] 6 [mypy-pygments.*]
10 ignore_missing_imports = True 7 ignore_missing_imports = True
package-lock.json
@@ -5,8 +5,8 @@ @@ -5,8 +5,8 @@
5 "packages": { 5 "packages": {
6 "": { 6 "": {
7 "dependencies": { 7 "dependencies": {
8 - "@fortawesome/fontawesome-free": "^5.15.2",  
9 - "codemirror": "^5.59.1", 8 + "@fortawesome/fontawesome-free": "^5.15.3",
  9 + "codemirror": "^5.59.4",
10 "mdbootstrap": "^4.19.2" 10 "mdbootstrap": "^4.19.2"
11 } 11 }
12 }, 12 },
@@ -20,9 +20,9 @@ @@ -20,9 +20,9 @@
20 } 20 }
21 }, 21 },
22 "node_modules/codemirror": { 22 "node_modules/codemirror": {
23 - "version": "5.61.1",  
24 - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz",  
25 - "integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" 23 + "version": "5.62.0",
  24 + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.0.tgz",
  25 + "integrity": "sha512-Xnl3304iCc8nyVZuRkzDVVwc794uc9QNX0UcPGeNic1fbzkSrO4l4GVXho9tRNKBgPYZXgocUqXyfIv3BILhCQ=="
26 }, 26 },
27 "node_modules/mdbootstrap": { 27 "node_modules/mdbootstrap": {
28 "version": "4.19.2", 28 "version": "4.19.2",
@@ -32,11 +32,14 @@ @@ -32,11 +32,14 @@
32 }, 32 },
33 "dependencies": { 33 "dependencies": {
34 "@fortawesome/fontawesome-free": { 34 "@fortawesome/fontawesome-free": {
  35 + "version": "5.15.3",
  36 + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-free/-/fontawesome-free-5.15.3.tgz",
  37 + "integrity": "sha512-rFnSUN/QOtnOAgqFRooTA3H57JLDm0QEG/jPdk+tLQNL/eWd+Aok8g3qCI+Q1xuDPWpGW/i9JySpJVsq8Q0s9w=="
35 }, 38 },
36 "codemirror": { 39 "codemirror": {
37 - "version": "5.61.1",  
38 - "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.61.1.tgz",  
39 - "integrity": "sha512-+D1NZjAucuzE93vJGbAaXzvoBHwp9nJZWWWF9utjv25+5AZUiah6CIlfb4ikG4MoDsFsCG8niiJH5++OO2LgIQ==" 40 + "version": "5.62.0",
  41 + "resolved": "https://registry.npmjs.org/codemirror/-/codemirror-5.62.0.tgz",
  42 + "integrity": "sha512-Xnl3304iCc8nyVZuRkzDVVwc794uc9QNX0UcPGeNic1fbzkSrO4l4GVXho9tRNKBgPYZXgocUqXyfIv3BILhCQ=="
40 }, 43 },
41 "mdbootstrap": { 44 "mdbootstrap": {
42 "version": "4.19.2", 45 "version": "4.19.2",
@@ -24,7 +24,7 @@ setup( @@ -24,7 +24,7 @@ setup(
24 'mistune', 24 'mistune',
25 'pyyaml>=5.1', 25 'pyyaml>=5.1',
26 'pygments', 26 'pygments',
27 - 'sqlalchemy<1.4', 27 + 'sqlalchemy>=1.4',
28 'bcrypt>=3.1', 28 'bcrypt>=3.1',
29 'networkx>=2.4' 29 'networkx>=2.4'
30 ], 30 ],