Ported code to Python 3.7. TODO: Port CI pipeline and deployment images.

This commit is contained in:
s3lph 2018-07-16 19:47:54 +02:00
parent f4685114b2
commit bb3d9e62e6
7 changed files with 36 additions and 125 deletions

View file

@ -16,7 +16,7 @@ This project intends to provide a well-tested and maintainable alternative to
## Dependencies ## Dependencies
- Python 3 (>=3.6) - Python 3 (>=3.7)
- Python dependencies: - Python dependencies:
- jinja2 - jinja2

View file

@ -1,4 +1,5 @@
from __future__ import annotations
from typing import List, Optional, Any, Type from typing import List, Optional, Any, Type
import crypt import crypt
@ -9,12 +10,6 @@ from matemat.exceptions import AuthenticationError, DatabaseConsistencyError
from matemat.db import DatabaseWrapper from matemat.db import DatabaseWrapper
from matemat.db.wrapper import Transaction from matemat.db.wrapper import Transaction
# TODO: Change to METHOD_BLOWFISH when adopting Python 3.7
"""
The method to use for password hashing.
"""
_CRYPT_METHOD = crypt.METHOD_SHA512
class MatematDatabase(object): class MatematDatabase(object):
""" """
@ -42,7 +37,7 @@ class MatematDatabase(object):
""" """
self.db: DatabaseWrapper = DatabaseWrapper(filename) self.db: DatabaseWrapper = DatabaseWrapper(filename)
def __enter__(self) -> 'MatematDatabase': def __enter__(self) -> MatematDatabase:
# Pass context manager stuff through to the database wrapper # Pass context manager stuff through to the database wrapper
self.db.__enter__() self.db.__enter__()
return self return self
@ -100,7 +95,7 @@ class MatematDatabase(object):
:raises ValueError: If a user with the same name already exists. :raises ValueError: If a user with the same name already exists.
""" """
# Hash the password. # Hash the password.
pwhash: str = crypt.crypt(password, crypt.mksalt(_CRYPT_METHOD)) pwhash: str = crypt.crypt(password, crypt.mksalt())
user_id: int = -1 user_id: int = -1
with self.db.transaction() as c: with self.db.transaction() as c:
# Look up whether a user with the same name already exists. # Look up whether a user with the same name already exists.
@ -178,7 +173,7 @@ class MatematDatabase(object):
if verify_password and not compare_digest(crypt.crypt(oldpass, row[0]), row[0]): if verify_password and not compare_digest(crypt.crypt(oldpass, row[0]), row[0]):
raise AuthenticationError('Old password does not match.') raise AuthenticationError('Old password does not match.')
# Hash the new password and write it to the database. # Hash the new password and write it to the database.
pwhash: str = crypt.crypt(newpass, crypt.mksalt(_CRYPT_METHOD)) pwhash: str = crypt.crypt(newpass, crypt.mksalt())
c.execute(''' c.execute('''
UPDATE users SET password = :pwhash, lastchange = STRFTIME('%s', 'now') WHERE user_id = :user_id UPDATE users SET password = :pwhash, lastchange = STRFTIME('%s', 'now') WHERE user_id = :user_id
''', { ''', {
@ -208,7 +203,7 @@ class MatematDatabase(object):
if verify_password and not compare_digest(crypt.crypt(password, row[0]), row[0]): if verify_password and not compare_digest(crypt.crypt(password, row[0]), row[0]):
raise AuthenticationError('Password does not match.') raise AuthenticationError('Password does not match.')
# Hash the new touchkey and write it to the database. # Hash the new touchkey and write it to the database.
tkhash: Optional[str] = crypt.crypt(touchkey, crypt.mksalt(_CRYPT_METHOD)) if touchkey is not None else None tkhash: Optional[str] = crypt.crypt(touchkey, crypt.mksalt()) if touchkey is not None else None
c.execute(''' c.execute('''
UPDATE users SET touchkey = :tkhash, lastchange = STRFTIME('%s', 'now') WHERE user_id = :user_id UPDATE users SET touchkey = :tkhash, lastchange = STRFTIME('%s', 'now') WHERE user_id = :user_id
''', { ''', {

View file

@ -58,7 +58,7 @@ class DatabaseTest(unittest.TestCase):
u = db.create_user('testuser', 'supersecurepassword', 'testuser@example.com') u = db.create_user('testuser', 'supersecurepassword', 'testuser@example.com')
# Add a touchkey without using the provided function # Add a touchkey without using the provided function
c.execute('''UPDATE users SET touchkey = :tkhash WHERE user_id = :user_id''', { c.execute('''UPDATE users SET touchkey = :tkhash WHERE user_id = :user_id''', {
'tkhash': crypt.crypt('0123', crypt.mksalt(crypt.METHOD_SHA512)), 'tkhash': crypt.crypt('0123', crypt.mksalt()),
'user_id': u.id 'user_id': u.id
}) })
user = db.login('testuser', 'supersecurepassword') user = db.login('testuser', 'supersecurepassword')
@ -99,7 +99,7 @@ class DatabaseTest(unittest.TestCase):
with self.assertRaises(AuthenticationError): with self.assertRaises(AuthenticationError):
db.login('testuser', 'mynewpassword') db.login('testuser', 'mynewpassword')
db.login('testuser', 'adminpasswordreset') db.login('testuser', 'adminpasswordreset')
user._user_id = -1 user.id = -1
with self.assertRaises(AuthenticationError): with self.assertRaises(AuthenticationError):
# Password change for an inexistent user should fail # Password change for an inexistent user should fail
db.change_password(user, 'adminpasswordreset', 'passwordwithoutuser') db.change_password(user, 'adminpasswordreset', 'passwordwithoutuser')
@ -123,7 +123,7 @@ class DatabaseTest(unittest.TestCase):
with self.assertRaises(AuthenticationError): with self.assertRaises(AuthenticationError):
db.login('testuser', touchkey='4567') db.login('testuser', touchkey='4567')
db.login('testuser', touchkey='89ab') db.login('testuser', touchkey='89ab')
user._user_id = -1 user.id = -1
with self.assertRaises(AuthenticationError): with self.assertRaises(AuthenticationError):
# Touchkey change for an inexistent user should fail # Touchkey change for an inexistent user should fail
db.change_touchkey(user, '89ab', '048c') db.change_touchkey(user, '89ab', '048c')
@ -139,7 +139,7 @@ class DatabaseTest(unittest.TestCase):
self.assertEqual('newaddress@example.com', checkuser.email) self.assertEqual('newaddress@example.com', checkuser.email)
self.assertFalse(checkuser.is_admin) self.assertFalse(checkuser.is_admin)
self.assertFalse(checkuser.is_member) self.assertFalse(checkuser.is_member)
user._user_id = -1 user.id = -1
with self.assertRaises(DatabaseConsistencyError): with self.assertRaises(DatabaseConsistencyError):
db.change_user(user) db.change_user(user)
@ -205,7 +205,7 @@ class DatabaseTest(unittest.TestCase):
self.assertEqual('Flora Power Mate', checkproduct.name) self.assertEqual('Flora Power Mate', checkproduct.name)
self.assertEqual(150, checkproduct.price_member) self.assertEqual(150, checkproduct.price_member)
self.assertEqual(250, checkproduct.price_non_member) self.assertEqual(250, checkproduct.price_non_member)
product._product_id = -1 product.id = -1
with self.assertRaises(DatabaseConsistencyError): with self.assertRaises(DatabaseConsistencyError):
db.change_product(product) db.change_product(product)
product2 = db.create_product('Club Mate', 200, 200) product2 = db.create_product('Club Mate', 200, 200)
@ -248,7 +248,7 @@ class DatabaseTest(unittest.TestCase):
with self.assertRaises(ValueError): with self.assertRaises(ValueError):
# Should fail, negative amount # Should fail, negative amount
db.deposit(user, -42) db.deposit(user, -42)
user._user_id = -1 user.id = -1
with self.assertRaises(DatabaseConsistencyError): with self.assertRaises(DatabaseConsistencyError):
# Should fail, user id -1 does not exist # Should fail, user id -1 does not exist
db.deposit(user, 42) db.deposit(user, 42)
@ -267,7 +267,7 @@ class DatabaseTest(unittest.TestCase):
self.assertEqual(42, c.fetchone()[0]) self.assertEqual(42, c.fetchone()[0])
c.execute('''SELECT stock FROM products WHERE product_id = ?''', [product2.id]) c.execute('''SELECT stock FROM products WHERE product_id = ?''', [product2.id])
self.assertEqual(0, c.fetchone()[0]) self.assertEqual(0, c.fetchone()[0])
product._product_id = -1 product.id = -1
with self.assertRaises(DatabaseConsistencyError): with self.assertRaises(DatabaseConsistencyError):
# Should fail, product id -1 does not exist # Should fail, product id -1 does not exist
db.restock(product, 42) db.restock(product, 42)

View file

@ -1,4 +1,5 @@
from __future__ import annotations
from typing import Any, Optional from typing import Any, Optional
import sqlite3 import sqlite3
@ -76,7 +77,7 @@ class DatabaseWrapper(object):
self._filename: str = filename self._filename: str = filename
self._sqlite_db: Optional[sqlite3.Connection] = None self._sqlite_db: Optional[sqlite3.Connection] = None
def __enter__(self) -> 'DatabaseWrapper': def __enter__(self) -> DatabaseWrapper:
self.connect() self.connect()
return self return self

View file

@ -1,51 +1,10 @@
from typing import Any from dataclasses import dataclass
class Product(object): @dataclass
class Product:
def __init__(self, id: int
product_id: int, name: str
name: str, price_member: int
price_member: int, price_non_member: int
price_non_member: int) -> None:
self._product_id: int = product_id
self._name: str = name
self._price_member: int = price_member
self._price_non_member: int = price_non_member
def __eq__(self, other: Any) -> bool:
if other is None or not isinstance(other, Product):
return False
return self._product_id == other._product_id \
and self._name == other._name \
and self._price_member == other._price_member \
and self._price_non_member == other._price_non_member
@property
def id(self) -> int:
return self._product_id
@property
def name(self) -> str:
return self._name
@name.setter
def name(self, name: str) -> None:
self._name = name
@property
def price_member(self) -> int:
return self._price_member
@price_member.setter
def price_member(self, price: int) -> None:
self._price_member = price
@property
def price_non_member(self) -> int:
return self._price_non_member
@price_non_member.setter
def price_non_member(self, price: int) -> None:
self._price_non_member = price

View file

@ -1,58 +1,13 @@
from typing import Optional, Any from typing import Optional
from dataclasses import dataclass
class User(object): @dataclass
class User:
def __init__(self, id: int
user_id: int, name: str
username: str, email: Optional[str] = None
email: Optional[str] = None, is_admin: bool = False
admin: bool = False, is_member: bool = False
member: bool = True) -> None:
self._user_id: int = user_id
self._username: str = username
self._email: Optional[str] = email
self._admin: bool = admin
self._member: bool = member
def __eq__(self, other: Any) -> bool:
if other is None or not isinstance(other, User):
return False
return self._user_id == other._user_id \
and self._username == other._username \
and self._email == other._email \
and self._admin == other._admin \
and self._member == other._member
@property
def id(self) -> int:
return self._user_id
@property
def name(self) -> str:
return self._username
@property
def email(self) -> Optional[str]:
return self._email
@email.setter
def email(self, email: str) -> None:
self._email = email
@property
def is_admin(self) -> bool:
return self._admin
@is_admin.setter
def is_admin(self, admin: bool) -> None:
self._admin = admin
@property
def is_member(self) -> bool:
return self._member
@is_member.setter
def is_member(self, member: bool) -> None:
self._member = member

View file

@ -1,4 +1,5 @@
from __future__ import annotations
from typing import Dict, Iterator, List, Tuple, Union from typing import Dict, Iterator, List, Tuple, Union
@ -24,7 +25,7 @@ class RequestArguments(object):
""" """
self.__container: Dict[str, RequestArgument] = dict() self.__container: Dict[str, RequestArgument] = dict()
def __getitem__(self, key: str) -> 'RequestArgument': def __getitem__(self, key: str) -> RequestArgument:
""" """
Retrieve the argument for the given name, creating it on the fly, if it doesn't exist. Retrieve the argument for the given name, creating it on the fly, if it doesn't exist.
@ -40,7 +41,7 @@ class RequestArguments(object):
# Return the argument for the name # Return the argument for the name
return self.__container[key] return self.__container[key]
def __getattr__(self, key: str) -> 'RequestArgument': def __getattr__(self, key: str) -> RequestArgument:
""" """
Syntactic sugar for accessing values with a name that can be used in Python attributes. The value will be Syntactic sugar for accessing values with a name that can be used in Python attributes. The value will be
returned as an immutable view. returned as an immutable view.
@ -278,7 +279,7 @@ class RequestArgument(object):
# Yield an immutable scalar view for each (ctype, value) element in the array # Yield an immutable scalar view for each (ctype, value) element in the array
yield _View(self.__name, v) yield _View(self.__name, v)
def __getitem__(self, index: Union[int, slice]) -> 'RequestArgument': def __getitem__(self, index: Union[int, slice]) -> RequestArgument:
""" """
Index the argument with either an int or a slice. The returned values are represented as immutable Index the argument with either an int or a slice. The returned values are represented as immutable
RequestArgument views. RequestArgument views.