forked from s3lph/matemat
Back to Python 3.6 for the sake of Debian packaging (initial test!)
This commit is contained in:
parent
960a1c8517
commit
ea57485cfb
12 changed files with 148 additions and 86 deletions
|
@ -1,5 +1,5 @@
|
||||||
---
|
---
|
||||||
image: s3lph/matemat-ci:20180802-02
|
image: s3lph/matemat-ci:20181103-01
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- test
|
- test
|
||||||
|
@ -17,9 +17,9 @@ test:
|
||||||
stage: test
|
stage: test
|
||||||
script:
|
script:
|
||||||
- pip3 install -e .
|
- pip3 install -e .
|
||||||
- sudo -u matemat python3.7 -m coverage run --rcfile=setup.cfg -m unittest discover matemat
|
- sudo -u matemat python3.6 -m coverage run --rcfile=setup.cfg -m unittest discover matemat
|
||||||
- sudo -u matemat python3.7 -m coverage combine
|
- sudo -u matemat python3.6 -m coverage combine
|
||||||
- sudo -u matemat python3.7 -m coverage report --rcfile=setup.cfg
|
- sudo -u matemat python3.6 -m coverage report --rcfile=setup.cfg
|
||||||
|
|
||||||
codestyle:
|
codestyle:
|
||||||
stage: test
|
stage: test
|
||||||
|
@ -45,8 +45,8 @@ build_docker:
|
||||||
build_wheel:
|
build_wheel:
|
||||||
stage: build
|
stage: build
|
||||||
script:
|
script:
|
||||||
- python3.7 setup.py egg_info --tag-build "+$CI_COMMIT_SHA" --tag-date bdist_wheel
|
- python3.6 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_REF_NAME" --tag-date bdist_wheel
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
paths:
|
||||||
- "dist/*.whl"
|
- "dist/*.whl"
|
||||||
|
@ -56,29 +56,28 @@ build_wheel:
|
||||||
- master
|
- master
|
||||||
- tags
|
- tags
|
||||||
|
|
||||||
# This is only useful once Debian either defaults python3 to 3.7 or provides python3.7- packages
|
build_deb:
|
||||||
# for the needed dependencies... But it SHOULD work...
|
stage: build
|
||||||
#build_deb:
|
script:
|
||||||
# stage: build
|
- cp -r static/ package/debian/matemat/usr/lib/matemat/static/
|
||||||
# script:
|
- cp -r templates/ package/debian/matemat/usr/lib/matemat/templates/
|
||||||
# - cp -r static/ package/debian/matemat/usr/lib/matemat/static/
|
- python3.6 setup.py egg_info -d -b +master install --root=package/debian/matemat/ --prefix=/usr --optimize=1
|
||||||
# - cp -r templates/ package/debian/matemat/usr/lib/matemat/templates/
|
- export PYTHON_BIN=$(which python3)
|
||||||
# - python3.7 setup.py egg_info -d -b +master install --root=package/debian/matemat/ --prefix=/usr --optimize=1
|
- cd package/debian
|
||||||
# - export PYTHON_BIN=$(which python3)
|
- mv matemat/usr/lib/python3.6/{site,dist}-packages
|
||||||
# - cd package/debian
|
- mv matemat/usr/bin/matemat matemat/usr/lib/matemat/matemat
|
||||||
# - mv matemat/usr/lib/python3.7/{site,dist}-packages
|
- rm -rf matemat/usr/bin
|
||||||
# - mv matemat/usr/bin/matemat matemat/usr/lib/matemat/matemat
|
- find . -type f -exec sed -re "s#${PYTHON_BIN}#/usr/bin/python3.6#g" -i {} \;
|
||||||
# - rm -rf matemat/usr/bin
|
- dpkg-deb --build matemat
|
||||||
# - find . -type f -exec sed -re "s#${PYTHON_BIN}#/usr/bin/python3.7#g" -i {} \;
|
- mv matemat.deb "matemat_${MATEMAT_VERSION}+${CI_COMMIT_REF_NAME}-1_all.deb"
|
||||||
# - dpkg-deb --build matemat
|
artifacts:
|
||||||
# - mv matemat.deb "matemat_${MATEMAT_VERSION}+${CI_COMMIT_REF_NAME}-1_all.deb"
|
paths:
|
||||||
# artifacts:
|
- "package/debian/*.deb"
|
||||||
# paths:
|
only:
|
||||||
# - "package/debian/*.deb"
|
- 27-deployment
|
||||||
# only:
|
- staging
|
||||||
# - staging
|
- master
|
||||||
# - master
|
- tags
|
||||||
# - tags
|
|
||||||
|
|
||||||
build_archlinux:
|
build_archlinux:
|
||||||
stage: build
|
stage: build
|
||||||
|
@ -99,7 +98,7 @@ build_archlinux:
|
||||||
paths:
|
paths:
|
||||||
- "package/archlinux/*.pkg.tar.xz"
|
- "package/archlinux/*.pkg.tar.xz"
|
||||||
only:
|
only:
|
||||||
- deployment
|
- 27-deployment
|
||||||
- staging
|
- staging
|
||||||
- master
|
- master
|
||||||
- tags
|
- tags
|
||||||
|
|
|
@ -16,7 +16,7 @@ This project intends to provide a well-tested and maintainable alternative to
|
||||||
|
|
||||||
## Dependencies
|
## Dependencies
|
||||||
|
|
||||||
- Python 3 (>=3.7)
|
- Python 3 (>=3.6)
|
||||||
- Python dependencies:
|
- Python dependencies:
|
||||||
- file-magic
|
- file-magic
|
||||||
- jinja2
|
- jinja2
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
from typing import Any, Dict, List, Optional, Tuple, Type
|
from typing import Any, Dict, List, Optional, Tuple, Type
|
||||||
|
|
||||||
import crypt
|
import crypt
|
||||||
|
@ -39,7 +38,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
|
||||||
|
|
|
@ -1,22 +1,19 @@
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Product:
|
class Product:
|
||||||
"""
|
"""
|
||||||
Representation of a product offered by the Matemat, with a name, prices for users, and the number of items
|
Representation of a product offered by the Matemat, with a name, prices for users, and the number of items
|
||||||
currently in stock.
|
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 name: The product's name.
|
||||||
:param price_member: The price of a unit of this product for users marked as "members".
|
: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 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.
|
:param stock: The number of items of this product currently in stock.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: int
|
def __init__(self, _id: int, name: str, price_member: int, price_non_member: int, stock: int) -> None:
|
||||||
name: str
|
self.id: int = _id
|
||||||
price_member: int
|
self.name: str = name
|
||||||
price_non_member: int
|
self.price_member: int = price_member
|
||||||
stock: int
|
self.price_non_member: int = price_non_member
|
||||||
|
self.stock: int = stock
|
||||||
|
|
|
@ -1,17 +1,30 @@
|
||||||
|
|
||||||
from typing import List
|
from typing import List
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from matemat.db.primitives import User, Transaction
|
from matemat.db.primitives import User, Transaction
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class Receipt:
|
class Receipt:
|
||||||
|
"""
|
||||||
|
Representation of a receipt for a user and a given timespan.
|
||||||
|
|
||||||
id: int
|
:param _id: The receipt ID in the database.
|
||||||
transactions: List[Transaction]
|
:param transactions: The list of transactions on this receipt.
|
||||||
user: User
|
:param user: The user for whom this receipt was issued.
|
||||||
from_date: datetime
|
:param from_date: The beginning of the time span this receipt covers.
|
||||||
to_date: datetime
|
: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
|
||||||
|
|
|
@ -11,7 +11,7 @@ class ReceiptPreference(Enum):
|
||||||
A user's preference for the frequency of receiving receipts.
|
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)
|
e = object.__new__(cls)
|
||||||
# The enum's internal value
|
# The enum's internal value
|
||||||
e._value_: int = args[0]
|
e._value_: int = args[0]
|
||||||
|
|
|
@ -1,6 +1,5 @@
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from dataclasses import dataclass
|
|
||||||
|
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
|
@ -8,14 +7,23 @@ from matemat.db.primitives import User
|
||||||
from matemat.util.currency_format import format_chf
|
from matemat.util.currency_format import format_chf
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Transaction:
|
class Transaction:
|
||||||
|
"""
|
||||||
|
Representation of a generic transaction involving an user and an amount of money.
|
||||||
|
|
||||||
id: int
|
:param _id: The transaction ID in the database.
|
||||||
user: User
|
:param user: The user affected by this transaction.
|
||||||
value: int
|
:param value: The monetary value of this transaction.
|
||||||
old_balance: int
|
:param old_balance: The balance on the user's account before this transaction.
|
||||||
date: datetime
|
: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
|
@property
|
||||||
def receipt_date(self) -> str:
|
def receipt_date(self) -> str:
|
||||||
|
@ -38,29 +46,71 @@ class Transaction:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Consumption(Transaction):
|
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
|
@property
|
||||||
def receipt_description(self) -> str:
|
def receipt_description(self) -> str:
|
||||||
return self.product
|
return self.product
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Deposit(Transaction):
|
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
|
@property
|
||||||
def receipt_description(self) -> str:
|
def receipt_description(self) -> str:
|
||||||
return 'Deposit'
|
return 'Deposit'
|
||||||
|
|
||||||
|
|
||||||
@dataclass(frozen=True)
|
|
||||||
class Modification(Transaction):
|
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
|
:param _id: The transaction ID in the database.
|
||||||
reason: Optional[str]
|
: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
|
@property
|
||||||
def receipt_description(self) -> str:
|
def receipt_description(self) -> str:
|
||||||
|
|
|
@ -1,30 +1,36 @@
|
||||||
|
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from dataclasses import dataclass
|
|
||||||
from matemat.db.primitives.ReceiptPreference import ReceiptPreference
|
from matemat.db.primitives.ReceiptPreference import ReceiptPreference
|
||||||
|
|
||||||
|
|
||||||
@dataclass
|
|
||||||
class User:
|
class User:
|
||||||
"""
|
"""
|
||||||
Representation of a user registered with the Matemat, with a name, e-mail address (optional), whether the user is a
|
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
|
member of the organization the Matemat instance is used in, whether the user is an administrator, and the user's
|
||||||
account balance.
|
account balance.
|
||||||
|
|
||||||
:param id: The user ID in the database.
|
:param _id: The user ID in the database.
|
||||||
:param username: The user's name.
|
:param name: The user's name.
|
||||||
:param balance: The balance of the user's account.
|
:param balance: The balance of the user's account.
|
||||||
:param email: The user's e-mail address (optional).
|
:param email: The user's e-mail address (optional).
|
||||||
:param admin: Whether the user is an administrator.
|
:param is_admin: Whether the user is an administrator.
|
||||||
:param member: Whether the user is a member.
|
:param is_member: Whether the user is a member.
|
||||||
:param receipt_pref: The user's preference on how often to receive transaction receipts.
|
:param receipt_pref: The user's preference on how often to receive transaction receipts.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
id: int
|
def __init__(self,
|
||||||
name: str
|
_id: int,
|
||||||
balance: int
|
name: str,
|
||||||
email: Optional[str] = None
|
balance: int,
|
||||||
is_admin: bool = False
|
email: Optional[str] = None,
|
||||||
is_member: bool = False
|
is_admin: bool = False,
|
||||||
receipt_pref: ReceiptPreference = ReceiptPreference.NONE
|
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
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
from typing import Any, Optional
|
from typing import Any, Optional
|
||||||
|
|
||||||
import sqlite3
|
import sqlite3
|
||||||
|
@ -49,7 +48,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
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
from __future__ import annotations
|
|
||||||
from typing import Dict, Iterator, List, Tuple, Union
|
from typing import Dict, Iterator, List, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
|
@ -25,7 +24,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.
|
||||||
|
|
||||||
|
@ -41,7 +40,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.
|
||||||
|
@ -279,7 +278,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.
|
||||||
|
|
2
setup.py
2
setup.py
|
@ -17,7 +17,7 @@ soda machine's touch screen).
|
||||||
''',
|
''',
|
||||||
|
|
||||||
packages=find_packages(exclude=['*.test']),
|
packages=find_packages(exclude=['*.test']),
|
||||||
python_requires='>=3.7',
|
python_requires='>=3.6',
|
||||||
install_requires=[
|
install_requires=[
|
||||||
'file-magic',
|
'file-magic',
|
||||||
'jinja2',
|
'jinja2',
|
||||||
|
|
|
@ -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.
|
# 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 \
|
RUN sed -re 's/stretch/buster/g' -i /etc/apt/sources.list \
|
||||||
&& useradd -d /home/matemat -m matemat \
|
&& 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 \
|
&& chown matemat:matemat -R /var/matemat/upload \
|
||||||
&& apt-get update -qy \
|
&& apt-get update -qy \
|
||||||
&& apt-get install -y --no-install-recommends file sudo openssh-client git docker.io build-essential \
|
&& 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/*
|
&& rm -rf /var/lib/apt/lists/*
|
||||||
|
|
||||||
WORKDIR /home/matemat
|
WORKDIR /home/matemat
|
||||||
|
|
Loading…
Reference in a new issue