From ea57485cfb6cd2292b45fd8bcd0880ecc71c3300 Mon Sep 17 00:00:00 2001 From: s3lph Date: Sat, 3 Nov 2018 18:28:28 +0100 Subject: [PATCH] Back to Python 3.6 for the sake of Debian packaging (initial test!) --- .gitlab-ci.yml | 59 +++++++++-------- README.md | 2 +- matemat/db/facade.py | 3 +- matemat/db/primitives/Product.py | 17 ++--- matemat/db/primitives/Receipt.py | 27 ++++++-- matemat/db/primitives/ReceiptPreference.py | 2 +- matemat/db/primitives/Transaction.py | 76 ++++++++++++++++++---- matemat/db/primitives/User.py | 32 +++++---- matemat/db/wrapper.py | 3 +- matemat/webserver/requestargs.py | 7 +- setup.py | 2 +- testing/Dockerfile | 4 +- 12 files changed, 148 insertions(+), 86 deletions(-) diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 27071e2..ab5e86d 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,5 +1,5 @@ --- -image: s3lph/matemat-ci:20180802-02 +image: s3lph/matemat-ci:20181103-01 stages: - test @@ -17,9 +17,9 @@ test: stage: test script: - pip3 install -e . - - sudo -u matemat python3.7 -m coverage run --rcfile=setup.cfg -m unittest discover matemat - - sudo -u matemat python3.7 -m coverage combine - - sudo -u matemat python3.7 -m coverage report --rcfile=setup.cfg + - sudo -u matemat python3.6 -m coverage run --rcfile=setup.cfg -m unittest discover matemat + - sudo -u matemat python3.6 -m coverage combine + - sudo -u matemat python3.6 -m coverage report --rcfile=setup.cfg codestyle: stage: test @@ -45,8 +45,8 @@ build_docker: build_wheel: stage: build script: - - python3.7 setup.py egg_info --tag-build "+$CI_COMMIT_SHA" --tag-date bdist_wheel - - python3.7 setup.py egg_info --tag-build "+$CI_COMMIT_REF_NAME" --tag-date bdist_wheel + - python3.6 setup.py egg_info --tag-build "+$CI_COMMIT_SHA" --tag-date bdist_wheel + - python3.6 setup.py egg_info --tag-build "+$CI_COMMIT_REF_NAME" --tag-date bdist_wheel artifacts: paths: - "dist/*.whl" @@ -56,29 +56,28 @@ build_wheel: - master - tags -# This is only useful once Debian either defaults python3 to 3.7 or provides python3.7- packages -# for the needed dependencies... But it SHOULD work... -#build_deb: -# stage: build -# script: -# - cp -r static/ package/debian/matemat/usr/lib/matemat/static/ -# - cp -r templates/ package/debian/matemat/usr/lib/matemat/templates/ -# - python3.7 setup.py egg_info -d -b +master install --root=package/debian/matemat/ --prefix=/usr --optimize=1 -# - export PYTHON_BIN=$(which python3) -# - cd package/debian -# - mv matemat/usr/lib/python3.7/{site,dist}-packages -# - mv matemat/usr/bin/matemat matemat/usr/lib/matemat/matemat -# - rm -rf matemat/usr/bin -# - find . -type f -exec sed -re "s#${PYTHON_BIN}#/usr/bin/python3.7#g" -i {} \; -# - dpkg-deb --build matemat -# - mv matemat.deb "matemat_${MATEMAT_VERSION}+${CI_COMMIT_REF_NAME}-1_all.deb" -# artifacts: -# paths: -# - "package/debian/*.deb" -# only: -# - staging -# - master -# - tags +build_deb: + stage: build + script: + - cp -r static/ package/debian/matemat/usr/lib/matemat/static/ + - cp -r templates/ package/debian/matemat/usr/lib/matemat/templates/ + - python3.6 setup.py egg_info -d -b +master install --root=package/debian/matemat/ --prefix=/usr --optimize=1 + - export PYTHON_BIN=$(which python3) + - cd package/debian + - mv matemat/usr/lib/python3.6/{site,dist}-packages + - mv matemat/usr/bin/matemat matemat/usr/lib/matemat/matemat + - rm -rf matemat/usr/bin + - find . -type f -exec sed -re "s#${PYTHON_BIN}#/usr/bin/python3.6#g" -i {} \; + - dpkg-deb --build matemat + - mv matemat.deb "matemat_${MATEMAT_VERSION}+${CI_COMMIT_REF_NAME}-1_all.deb" + artifacts: + paths: + - "package/debian/*.deb" + only: + - 27-deployment + - staging + - master + - tags build_archlinux: stage: build @@ -99,7 +98,7 @@ build_archlinux: paths: - "package/archlinux/*.pkg.tar.xz" only: - - deployment + - 27-deployment - staging - master - tags diff --git a/README.md b/README.md index 1d75157..2902bfa 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ This project intends to provide a well-tested and maintainable alternative to ## Dependencies -- Python 3 (>=3.7) +- Python 3 (>=3.6) - Python dependencies: - file-magic - jinja2 diff --git a/matemat/db/facade.py b/matemat/db/facade.py index 1c8061f..c32df0a 100644 --- a/matemat/db/facade.py +++ b/matemat/db/facade.py @@ -1,5 +1,4 @@ -from __future__ import annotations from typing import Any, Dict, List, Optional, Tuple, Type import crypt @@ -39,7 +38,7 @@ class MatematDatabase(object): """ self.db: DatabaseWrapper = DatabaseWrapper(filename) - def __enter__(self) -> MatematDatabase: + def __enter__(self) -> 'MatematDatabase': # Pass context manager stuff through to the database wrapper self.db.__enter__() return self diff --git a/matemat/db/primitives/Product.py b/matemat/db/primitives/Product.py index 18b5003..c543630 100644 --- a/matemat/db/primitives/Product.py +++ b/matemat/db/primitives/Product.py @@ -1,22 +1,19 @@ -from dataclasses import dataclass - - -@dataclass class Product: """ Representation of a product offered by the Matemat, with a name, prices for users, and the number of items currently in stock. - :param id: The product ID in the database. + :param _id: The product ID in the database. :param name: The product's name. :param price_member: The price of a unit of this product for users marked as "members". :param price_non_member: The price of a unit of this product for users NOT marked as "members". :param stock: The number of items of this product currently in stock. """ - id: int - name: str - price_member: int - price_non_member: int - stock: int + def __init__(self, _id: int, name: str, price_member: int, price_non_member: int, stock: int) -> None: + self.id: int = _id + self.name: str = name + self.price_member: int = price_member + self.price_non_member: int = price_non_member + self.stock: int = stock diff --git a/matemat/db/primitives/Receipt.py b/matemat/db/primitives/Receipt.py index 2ddbe12..29f5d4b 100644 --- a/matemat/db/primitives/Receipt.py +++ b/matemat/db/primitives/Receipt.py @@ -1,17 +1,30 @@ from typing import List -from dataclasses import dataclass from datetime import datetime from matemat.db.primitives import User, Transaction -@dataclass class Receipt: + """ + Representation of a receipt for a user and a given timespan. - id: int - transactions: List[Transaction] - user: User - from_date: datetime - to_date: datetime + :param _id: The receipt ID in the database. + :param transactions: The list of transactions on this receipt. + :param user: The user for whom this receipt was issued. + :param from_date: The beginning of the time span this receipt covers. + :param to_date: The end of the time span this receipt covers. + """ + + def __init__(self, + _id: int, + transactions: List[Transaction], + user: User, + from_date: datetime, + to_date: datetime) -> None: + self.id: int = _id + self.transactions: List[Transaction] = transactions + self.user: User = user + self.from_date: datetime = from_date + self.to_date: datetime = to_date diff --git a/matemat/db/primitives/ReceiptPreference.py b/matemat/db/primitives/ReceiptPreference.py index fb15f18..8afaab5 100644 --- a/matemat/db/primitives/ReceiptPreference.py +++ b/matemat/db/primitives/ReceiptPreference.py @@ -11,7 +11,7 @@ class ReceiptPreference(Enum): A user's preference for the frequency of receiving receipts. """ - def __new__(cls, *args, **kwargs): + def __new__(cls, *args, **kwargs) -> 'ReceiptPreference': e = object.__new__(cls) # The enum's internal value e._value_: int = args[0] diff --git a/matemat/db/primitives/Transaction.py b/matemat/db/primitives/Transaction.py index 964b835..c2777ea 100644 --- a/matemat/db/primitives/Transaction.py +++ b/matemat/db/primitives/Transaction.py @@ -1,6 +1,5 @@ from typing import Optional -from dataclasses import dataclass from datetime import datetime @@ -8,14 +7,23 @@ from matemat.db.primitives import User from matemat.util.currency_format import format_chf -@dataclass(frozen=True) class Transaction: + """ + Representation of a generic transaction involving an user and an amount of money. - id: int - user: User - value: int - old_balance: int - date: datetime + :param _id: The transaction ID in the database. + :param user: The user affected by this transaction. + :param value: The monetary value of this transaction. + :param old_balance: The balance on the user's account before this transaction. + :param date: The date of this transaction. + """ + + def __init__(self, _id: int, user: User, value: int, old_balance: int, date: datetime) -> None: + self.id: int = _id + self.user: User = user + self.value: int = value + self.old_balance: int = old_balance + self.date: datetime = date @property def receipt_date(self) -> str: @@ -38,29 +46,71 @@ class Transaction: return None -@dataclass(frozen=True) class Consumption(Transaction): + """ + Representation of a consumption involving an user, a product and an amount of money. - product: str + :param _id: The transaction ID in the database. + :param user: The user affected by this transaction. + :param value: The (negative) price of the product that was bought. + :param old_balance: The balance on the user's account before this transaction. + :param date: The date of this transaction. + :param product: The name of the product that was bought. + """ + + def __init__(self, _id: int, user: User, value: int, old_balance: int, date: datetime, product: str) -> None: + super().__init__(_id, user, value, old_balance, date) + self.product: str = product @property def receipt_description(self) -> str: return self.product -@dataclass(frozen=True) class Deposit(Transaction): + """ + Representation of a deposit involving an user and an amount of money. + + :param _id: The transaction ID in the database. + :param user: The user affected by this transaction. + :param value: The amount of money that was deposited on the account. + :param old_balance: The balance on the user's account before this transaction. + :param date: The date of this transaction. + """ + + def __init__(self, _id: int, user: User, value: int, old_balance: int, date: datetime) -> None: + super().__init__(_id, user, value, old_balance, date) @property def receipt_description(self) -> str: return 'Deposit' -@dataclass(frozen=True) class Modification(Transaction): + """ + Representation of a administrative account balance modification. Involves the affected user, the agent that + performed the modification and optionally a message for the reason of the modification. - agent: str - reason: Optional[str] + :param _id: The transaction ID in the database. + :param user: The user affected by this transaction. + :param value: The amount of money that was deposited on the account. + :param old_balance: The balance on the user's account before this transaction. + :param date: The date of this transaction. + :param agent: The username of the agent performing the modification. + :param reason: The reason for this modification, as provided by the agent. + """ + + def __init__(self, + _id: int, + user: User, + value: int, + old_balance: int, + date: datetime, + agent: str, + reason: Optional[str]) -> None: + super().__init__(_id, user, value, old_balance, date) + self.agent: str = agent + self.reason: Optional[str] = reason @property def receipt_description(self) -> str: diff --git a/matemat/db/primitives/User.py b/matemat/db/primitives/User.py index 3bf25cc..01f8032 100644 --- a/matemat/db/primitives/User.py +++ b/matemat/db/primitives/User.py @@ -1,30 +1,36 @@ from typing import Optional -from dataclasses import dataclass from matemat.db.primitives.ReceiptPreference import ReceiptPreference -@dataclass class User: """ Representation of a user registered with the Matemat, with a name, e-mail address (optional), whether the user is a member of the organization the Matemat instance is used in, whether the user is an administrator, and the user's account balance. - :param id: The user ID in the database. - :param username: The user's name. + :param _id: The user ID in the database. + :param name: The user's name. :param balance: The balance of the user's account. :param email: The user's e-mail address (optional). - :param admin: Whether the user is an administrator. - :param member: Whether the user is a member. + :param is_admin: Whether the user is an administrator. + :param is_member: Whether the user is a member. :param receipt_pref: The user's preference on how often to receive transaction receipts. """ - id: int - name: str - balance: int - email: Optional[str] = None - is_admin: bool = False - is_member: bool = False - receipt_pref: ReceiptPreference = ReceiptPreference.NONE + def __init__(self, + _id: int, + name: str, + balance: int, + email: Optional[str] = None, + is_admin: bool = False, + is_member: bool = False, + receipt_pref: ReceiptPreference = ReceiptPreference.NONE) -> None: + self.id: int = _id + self.name: str = name + self.balance: int = balance + self.email: Optional[str] = email + self.is_admin: bool = is_admin + self.is_member: bool = is_member + self.receipt_pref: ReceiptPreference = receipt_pref diff --git a/matemat/db/wrapper.py b/matemat/db/wrapper.py index 4460680..3ba5797 100644 --- a/matemat/db/wrapper.py +++ b/matemat/db/wrapper.py @@ -1,5 +1,4 @@ -from __future__ import annotations from typing import Any, Optional import sqlite3 @@ -49,7 +48,7 @@ class DatabaseWrapper(object): self._filename: str = filename self._sqlite_db: Optional[sqlite3.Connection] = None - def __enter__(self) -> DatabaseWrapper: + def __enter__(self) -> 'DatabaseWrapper': self.connect() return self diff --git a/matemat/webserver/requestargs.py b/matemat/webserver/requestargs.py index 6ce11ef..373db90 100644 --- a/matemat/webserver/requestargs.py +++ b/matemat/webserver/requestargs.py @@ -1,5 +1,4 @@ -from __future__ import annotations from typing import Dict, Iterator, List, Tuple, Union @@ -25,7 +24,7 @@ class RequestArguments(object): """ 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. @@ -41,7 +40,7 @@ class RequestArguments(object): # Return the argument for the name 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 returned as an immutable view. @@ -279,7 +278,7 @@ class RequestArgument(object): # Yield an immutable scalar view for each (ctype, value) element in the array 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 RequestArgument views. diff --git a/setup.py b/setup.py index 265db5c..6c2d005 100644 --- a/setup.py +++ b/setup.py @@ -17,7 +17,7 @@ soda machine's touch screen). ''', packages=find_packages(exclude=['*.test']), - python_requires='>=3.7', + python_requires='>=3.6', install_requires=[ 'file-magic', 'jinja2', diff --git a/testing/Dockerfile b/testing/Dockerfile index a67d602..6b9cfab 100644 --- a/testing/Dockerfile +++ b/testing/Dockerfile @@ -1,6 +1,6 @@ # There is no buster image yet and stretch doesn't have a docker package. So let's just "upgrade" the image to buster. -FROM python:3.7-stretch +FROM python:3.6-stretch RUN sed -re 's/stretch/buster/g' -i /etc/apt/sources.list \ && useradd -d /home/matemat -m matemat \ @@ -9,7 +9,7 @@ RUN sed -re 's/stretch/buster/g' -i /etc/apt/sources.list \ && chown matemat:matemat -R /var/matemat/upload \ && apt-get update -qy \ && apt-get install -y --no-install-recommends file sudo openssh-client git docker.io build-essential \ - && python3.7 -m pip install coverage wheel pycodestyle mypy \ + && python3.6 -m pip install coverage wheel pycodestyle mypy \ && rm -rf /var/lib/apt/lists/* WORKDIR /home/matemat