Working on better folder structure

Trying to get a better maintainability by splitting the code in better
folders.
This commit is contained in:
Lunaresk 2022-01-17 12:54:24 +01:00
parent 7ca0f576c8
commit d52e5a63d5
5 changed files with 244 additions and 0 deletions

View File

@ -0,0 +1,5 @@
from flask import Flask
app = Flask(__name__)
from app import views

141
server/src/app/database.py Normal file
View File

@ -0,0 +1,141 @@
from os.path import dirname
from psycopg2 import connect as psyconn, ProgrammingError, errors
from yaml import safe_load
import logging
DIR = dirname(__file__) + "/"
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
logFormatter = logging.Formatter(
"%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s")
fileHandler = logging.FileHandler(DIR + "../../logs/database.log")
fileHandler.setFormatter(logFormatter)
fileHandler.setLevel(logging.INFO)
LOGGER.addHandler(fileHandler)
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.DEBUG)
LOGGER.addHandler(consoleHandler)
class Database:
def __init__(self, **kwargs):
pass
def connect(self, **kwargs):
with open(DIR + 'config.yaml', 'r') as file:
data = safe_load(file)['database']
LOGGER.debug('Merging passed arguments with default arguments.')
for key, value in data.items():
if key not in kwargs or not kwargs[key]:
kwargs[key] = value
LOGGER.info('Connecting to Database.')
self.conn = psyconn(host=kwargs["host"], port=kwargs["port"], dbname=kwargs["database"],
user=kwargs["user"], password=kwargs["password"])
self.conn.autocommit = True
def test_connection(self):
if not hasattr(self, "conn"):
LOGGER.info("Connection was not set, setting...")
self.connect()
else:
try:
with self.conn.cursor() as cursor:
try:
cursor.execute("SELECT 1;")
cursor.fetchall()
except:
LOGGER.warn(
'Connection seem to timed out, reconnecting...')
self.connect()
except:
LOGGER.warn('Connection seem to timed out, reconnecting...')
self.connect()
def connectionpersistence(func):
def wrapper(*args, **kwargs):
self = args[0]
self.test_connection()
return func(*args, **kwargs)
return wrapper
@connectionpersistence
def get_user(self, **kwargs):
result = ()
if 'login' in kwargs:
query = "SELECT login FROM users WHERE login = %(login)s;"
with self.conn.cursor() as cursor:
cursor.execute(query, kwargs)
try:
result = cursor.fetchall()
except ProgrammingError as e:
LOGGER.exception("")
except Exception as e:
LOGGER.exception("")
return result
@connectionpersistence
def get_report(self, **kwargs) -> list:
query = "SELECT u.name, bp.date, i.name, bp.amount, bp.price FROM bought_with_prices bp INNER JOIN items i ON bp.item = i.id INNER JOIN users u ON bp.user = u.login"
if kwargs:
query += " WHERE "
tempquery = []
if "user" in kwargs and kwargs['user']:
tempquery.append(f"bp.user = '{kwargs['user']}'")
if "year" in kwargs and kwargs['year']:
tempstring = "bp.date BETWEEN "
if "month" in kwargs and kwargs['month']:
tempstring += f"'{kwargs['year']}-{kwargs['month']}-01' AND "
tempstring += f"'{kwargs['year']+1}-01-01'" if kwargs['month'] == 12 else f"'{kwargs['year']}-{kwargs['month']+1}-01'"
else:
tempstring += f"'{kwargs['year']}-01-01' AND '{kwargs['year']+1}-01-01'"
tempstring += "::date - INTERVAL '1' DAY"
tempquery.append(tempstring)
query += " AND ".join(tempquery)
query += " ORDER BY u.name, bp.date, i.name ASC;"
LOGGER.debug(f"Executing query: {query}")
result = []
with self.conn.cursor() as cursor:
cursor.execute(query)
try:
result = cursor.fetchall()
except ProgrammingError as e:
LOGGER.exception("")
except Exception as e:
LOGGER.exception("")
return result
@connectionpersistence
def insert_bought_items(self, user: str, items: dict, date: str = None):
temp = ['"user", item, amount', "%(user)s, %(item)s, %(amount)s",
"bought.user = %(user)s AND bought.item = %(item)s AND bought.date = " + ("%(date)s" if date else "NOW()::date")]
if date:
temp[0] += ", date"
temp[1] += ", %(date)s"
values = [{'user': user, 'item': int(key), 'amount': value, 'date': date} for key, value in items.items()]
else:
values = [{'user': user, 'item': int(key), 'amount': value} for key, value in items.items()]
query = f"INSERT INTO bought({temp[0]}) VALUES({temp[1]}) ON CONFLICT ON CONSTRAINT bought_user_item_date DO UPDATE SET amount = bought.amount + %(amount)s WHERE {temp[2]};"
with self.conn.cursor() as cursor:
failed = {}
for value in values:
try:
cursor.execute(query, value)
except errors.ForeignKeyViolation as e:
if failed:
failed['items'][value['item']] = value['amount']
else:
failed = {'user': user, 'items': {value['item']: value['amount']}}
if date:
failed['date'] = date
LOGGER.exception("")
except Exception as e:
LOGGER.exception("")
return failed
def __delete__(self):
self.conn.close()

92
server/src/app/views.py Normal file
View File

@ -0,0 +1,92 @@
from app import app
from app.database import Database
from flask import abort, request
from flask.json import jsonify
from os import makedirs
from os.path import dirname, exists
import logging
DIR = dirname(__file__) + "/"
if not exists(DIR + "../logs"):
makedirs(DIR + "../logs")
LOGGER = logging.getLogger(__name__)
LOGGER.setLevel(logging.DEBUG)
logFormatter = logging.Formatter(
"%(asctime)s [%(threadName)s] [%(levelname)s] %(message)s")
fileHandler = logging.FileHandler(DIR + "../../logs/server.log")
fileHandler.setFormatter(logFormatter)
fileHandler.setLevel(logging.INFO)
LOGGER.addHandler(fileHandler)
consoleHandler = logging.StreamHandler()
consoleHandler.setLevel(logging.DEBUG)
LOGGER.addHandler(consoleHandler)
DATABASE = Database()
APPNAME = "scan2kasse"
@app.route('/')
def index():
return "<h1>Hello, World!</h>", 200
@app.route(f'/{APPNAME}/login')
def login():
if not request.json or 'login' not in request.json:
abort(400)
if not DATABASE.get_user(login = request.json['login']):
abort(403)
return jsonify({}), 200
@app.route(f'/{APPNAME}/insert', methods=['POST'])
def insert():
match request.json:
case {'user': user, 'items': items, 'date': date}:
failed = DATABASE.insert_bought_items(user, items, date)
if failed:
return jsonify(failed), 400
return jsonify({'inserted': True}), 201
case {'user': user, 'items': items}:
failed = DATABASE.insert_bought_items(user, items)
if failed:
return jsonify(failed), 400
return jsonify({'inserted': True}), 201
case _:
abort(400)
@app.route(f'/{APPNAME}/<string:user>', methods=['GET'])
@app.route(f'/{APPNAME}/<int:year>/<int:month>', methods=['GET'])
@app.route(f'/{APPNAME}/<string:user>/<int:year>', methods=['GET'])
@app.route(f'/{APPNAME}/<string:user>/<int:year>/<int:month>', methods=['GET'])
def get_monthly_report_from_user(user: str = None, year: int = None, month: int = None):
if month and (month > 12 or month < 1):
abort(400)
LOGGER.info("Getting results.")
results = DATABASE.get_report(user=user, year=year, month=month)
LOGGER.debug(f"Results received: {results}")
if results:
result_dict = group_results(results)
else:
result_dict = {}
return jsonify(result_dict)
def group_results(results: tuple) -> dict:
result_dict = {}
LOGGER.debug("Grouping...")
for result in results:
if result[0] not in result_dict:
result_dict[result[0]] = {}
if str(result[1]) not in result_dict[result[0]]:
result_dict[result[0]][str(result[1])] = {}
result_dict[result[0]][str(result[1])][result[2]] = (
result[3], result[4])
LOGGER.debug("Grouped.")
return result_dict

View File

6
server/src/run.py Normal file
View File

@ -0,0 +1,6 @@
from app import app
from gevent.pywsgi import WSGIServer
if __name__ == '__main__':
http_server = WSGIServer(('', 5000), app)
http_server.serve_forever()