Improve typing
All checks were successful
Build Formula10 Docker Image / build-docker (push) Successful in 23s

This commit is contained in:
2024-02-20 13:45:16 +01:00
parent b6f7cd85cb
commit b1d05cf7c8
5 changed files with 97 additions and 99 deletions

View File

@ -1,10 +1,11 @@
import csv
import os.path
from model import *
from typing import List, Any
from flask_sqlalchemy import SQLAlchemy
from model import Team, Driver, Race, User, RaceResult, RaceGuess, TeamWinners, PodiumDrivers, SeasonGuess
def load_csv(filename):
def load_csv(filename: str) -> List[List[str]]:
if not os.path.exists(filename):
print(f"Could not load data from file {filename}, as it doesn't exist!")
return []
@ -15,7 +16,7 @@ def load_csv(filename):
return list(reader)
def write_csv(filename, objects):
def write_csv(filename: str, objects: List[Any]):
if len(objects) == 0:
print(f"Could not write objects to file {filename}, as no objects were given!")
return
@ -28,15 +29,15 @@ def write_csv(filename, objects):
# Reload static database data, this has to be called from the app context
def reload_static_data(db):
def reload_static_data(db: SQLAlchemy):
print("Initializing Database with Static Values...")
# Create it (if it doesn't exist!)
db.create_all()
# Clear static data
Team.query.delete()
Driver.query.delete()
Race.query.delete()
db.session.query(Team).delete()
db.session.query(Driver).delete()
db.session.query(Race).delete()
# Reload static data
for row in load_csv("static_data/teams.csv"):
@ -49,18 +50,18 @@ def reload_static_data(db):
db.session.commit()
def reload_dynamic_data(db):
def reload_dynamic_data(db: SQLAlchemy):
print("Initializing Database with Dynamic Values...")
# Create it (if it doesn't exist!)
db.create_all()
# Clear dynamic data
User.query.delete()
RaceResult.query.delete()
RaceGuess.query.delete()
TeamWinners.query.delete()
PodiumDrivers.query.delete()
SeasonGuess.query.delete()
db.session.query(User).delete()
db.session.query(RaceResult).delete()
db.session.query(RaceGuess).delete()
db.session.query(TeamWinners).delete()
db.session.query(PodiumDrivers).delete()
db.session.query(SeasonGuess).delete()
# Reload dynamic data
for row in load_csv("dynamic_data/users.csv"):
@ -82,12 +83,12 @@ def reload_dynamic_data(db):
def export_dynamic_data():
print("Exporting Userdata...")
users = User.query.all()
raceresults = RaceResult.query.all()
raceguesses = RaceGuess.query.all()
teamwinners = TeamWinners.query.all()
podiumdrivers = PodiumDrivers.query.all()
seasonguesses = SeasonGuess.query.all()
users: List[User] = User.query.all()
raceresults: List[RaceResult] = RaceResult.query.all()
raceguesses: List[RaceGuess] = RaceGuess.query.all()
teamwinners: List[TeamWinners] = TeamWinners.query.all()
podiumdrivers: List[PodiumDrivers] = PodiumDrivers.query.all()
seasonguesses: List[SeasonGuess] = SeasonGuess.query.all()
write_csv("dynamic_data/users.csv", users)
write_csv("dynamic_data/raceresults.csv", raceresults)

View File

@ -1,6 +1,6 @@
from urllib.parse import unquote
from flask import Flask, render_template, request, redirect
from werkzeug import Response
from model import *
from database_utils import reload_static_data, reload_dynamic_data, export_dynamic_data
from template_model import TemplateModel
@ -30,42 +30,42 @@ db.init_app(app)
@app.route("/")
def root():
return race_active_user("Everyone")
def root() -> Response:
return redirect("/race/Everyone")
@app.route("/save/all", strict_slashes=False)
def save():
@app.route("/save/all")
def save() -> Response:
export_dynamic_data()
return redirect("/")
@app.route("/load/all")
def load():
def load() -> Response:
reload_static_data(db)
reload_dynamic_data(db)
return redirect("/")
@app.route("/load/static")
def load_static():
def load_static() -> Response:
reload_static_data(db)
return redirect("/")
@app.route("/load/dynamic")
def load_dynamic():
def load_dynamic() -> Response:
reload_dynamic_data(db)
return redirect("/")
@app.route("/race")
def race_root():
def race_root() -> Response:
return redirect("/race/Everyone")
@app.route("/race/<user_name>")
def race_active_user(user_name: str):
def race_active_user(user_name: str) -> str:
user_name = unquote(user_name)
model = TemplateModel()
return render_template("race.jinja",
@ -74,7 +74,7 @@ def race_active_user(user_name: str):
@app.route("/race-guess/<race_name>/<user_name>", methods=["POST"])
def race_guess_post(race_name: str, user_name: str):
def race_guess_post(race_name: str, user_name: str) -> Response:
race_name = unquote(race_name)
user_name = unquote(user_name)
@ -88,7 +88,7 @@ def race_guess_post(race_name: str, user_name: str):
print("Error: Can't guess race result if the race result is already known!")
return redirect(f"/race/{quote(user_name)}")
raceguess: RaceGuess | None = RaceGuess.query.filter_by(user_name=user_name, race_name=race_name).first()
raceguess: RaceGuess | None = db.session.query(RaceGuess).filter_by(user_name=user_name, race_name=race_name).first()
if raceguess is None:
raceguess = RaceGuess()
@ -104,12 +104,12 @@ def race_guess_post(race_name: str, user_name: str):
@app.route("/season")
def season_root():
def season_root() -> Response:
return redirect("/season/Everyone")
@app.route("/season/<user_name>")
def season_active_user(user_name: str):
def season_active_user(user_name: str) -> str:
user_name = unquote(user_name)
model = TemplateModel()
return render_template("season.jinja",
@ -118,7 +118,7 @@ def season_active_user(user_name: str):
@app.route("/season-guess/<user_name>", methods=["POST"])
def season_guess_post(user_name: str):
def season_guess_post(user_name: str) -> Response:
user_name = unquote(user_name)
guesses: List[str | None] = [
request.form.get("hottakeselect"),
@ -129,7 +129,7 @@ def season_guess_post(user_name: str):
request.form.get("lostselect")
]
teamwinnerguesses: List[str | None] = [
request.form.get(f"teamwinner-{team.name}") for team in Team.query.all()
request.form.get(f"teamwinner-{team.name}") for team in db.session.query(Team).all()
]
podiumdriverguesses: List[str] = request.form.getlist("podiumdrivers")
@ -137,7 +137,7 @@ def season_guess_post(user_name: str):
print("Error: /guessseason could not obtain request data!")
return redirect(f"/season/{quote(user_name)}")
seasonguess: SeasonGuess | None = SeasonGuess.query.filter_by(user_name=user_name).first()
seasonguess: SeasonGuess | None = db.session.query(SeasonGuess).filter_by(user_name=user_name).first()
teamwinners: TeamWinners | None = seasonguess.team_winners if seasonguess is not None else None
podiumdrivers: PodiumDrivers | None = seasonguess.podium_drivers if seasonguess is not None else None
@ -176,12 +176,12 @@ def season_guess_post(user_name: str):
@app.route("/result")
def result_root():
def result_root() -> Response:
return redirect("/result/Current")
@app.route("/result/<race_name>")
def result_active_race(race_name: str):
def result_active_race(race_name: str) -> str:
race_name = unquote(race_name)
model = TemplateModel()
return render_template("enter.jinja",
@ -190,7 +190,7 @@ def result_active_race(race_name: str):
@app.route("/result-enter/<result_race_name>", methods=["POST"])
def result_enter_post(result_race_name: str):
def result_enter_post(result_race_name: str) -> Response:
result_race_name = unquote(result_race_name)
pxxs: List[str] = request.form.getlist("pxxdrivers")
dnfs: List[str] = request.form.getlist("dnf-drivers")
@ -200,7 +200,7 @@ def result_enter_post(result_race_name: str):
pxxs_dict: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxxs)}
dnfs_dict: Dict[str, str] = {str(position + 1): driver for position, driver in enumerate(pxxs) if driver in dnfs}
raceresult: RaceResult | None = RaceResult.query.filter_by(race_name=result_race_name).first()
raceresult: RaceResult | None = db.session.query(RaceResult).filter_by(race_name=result_race_name).first()
if raceresult is None:
raceresult = RaceResult()
@ -212,7 +212,7 @@ def result_enter_post(result_race_name: str):
raceresult.excluded_driver_names = excludes
db.session.commit()
race: Race | None = Race.query.filter_by(name=result_race_name).first()
race: Race | None = db.session.query(Race).filter_by(name=result_race_name).first()
if race is None:
print("Error: Can't redirect to /enter/<GrandPrix> because race couldn't be found")
return redirect(f"/result/Current")
@ -221,7 +221,7 @@ def result_enter_post(result_race_name: str):
@app.route("/user")
def user_root():
def user_root() -> str:
users: List[User] = User.query.all()
return render_template("users.jinja",
@ -229,14 +229,14 @@ def user_root():
@app.route("/user-add", methods=["POST"])
def user_add_post():
def user_add_post() -> Response:
username: str | None = request.form.get("select-add-user")
if username is None or len(username) == 0:
print(f"Not adding user, since no username was received")
return user_root()
return redirect("/user")
if len(User.query.filter_by(name=username).all()) > 0:
if len(db.session.query(User).filter_by(name=username).all()) > 0:
print(f"Not adding user {username}: Already exists!")
return redirect("/user")
@ -249,7 +249,7 @@ def user_add_post():
@app.route("/user-delete", methods=["POST"])
def user_delete_post():
def user_delete_post() -> Response:
username = request.form.get("select-delete-user")
if username is None or len(username) == 0:

View File

@ -1,13 +1,12 @@
import json
from datetime import datetime
from typing import List, Dict
from typing import Any, List, Dict
from urllib.parse import quote
from flask_sqlalchemy import SQLAlchemy
from sqlalchemy import Integer, String, DateTime, ForeignKey
from sqlalchemy.orm import Mapped, mapped_column, relationship
db = SQLAlchemy()
db: SQLAlchemy = SQLAlchemy()
####################################
# Static Data (Defined in Backend) #
@ -21,7 +20,7 @@ class Race(db.Model):
"""
__tablename__ = "race"
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.name = str(row[0])
self.number = int(row[1])
self.date = datetime.strptime(row[2], "%Y-%m-%d")
@ -44,7 +43,7 @@ class Team(db.Model):
"""
__tablename__ = "team"
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.name = str(row[0])
return self
@ -58,7 +57,7 @@ class Driver(db.Model):
"""
__tablename__ = "driver"
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.name = str(row[0])
self.abbr = str(row[1])
self.team_name = str(row[2])
@ -86,11 +85,11 @@ class User(db.Model):
__tablename__ = "user"
__csv_header__ = ["name"]
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.name = str(row[0])
return self
def to_csv(self):
def to_csv(self) -> List[Any]:
return [
self.name
]
@ -111,14 +110,14 @@ class RaceResult(db.Model):
__allow_unmapped__ = True # TODO: Used for json conversion, move this to some other class instead
__csv_header__ = ["race_name", "pxx_driver_names_json", "dnf_driver_names_json", "excluded_driver_names_json"]
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.race_name = str(row[0])
self.pxx_driver_names_json = str(row[1])
self.dnf_driver_names_json = str(row[2])
self.excluded_driver_names_json = str(row[3])
return self
def to_csv(self):
def to_csv(self) -> List[Any]:
return [
self.race_name,
self.pxx_driver_names_json,
@ -166,7 +165,7 @@ class RaceResult(db.Model):
if self._pxx_drivers is None:
self._pxx_drivers = dict()
for position, driver_name in self.pxx_driver_names.items():
driver = Driver.query.filter_by(name=driver_name).first()
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
if driver is None:
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
@ -176,11 +175,11 @@ class RaceResult(db.Model):
@property
def pxx_drivers_values(self) -> List[Driver]:
drivers: List[Driver] = []
drivers: List[Driver] = list()
# I don't know what order dict.values() etc. will return...
for position in range(1, 21):
drivers += [self.pxx_drivers[str(position)]]
drivers.append(self.pxx_drivers[str(position)])
return drivers
@ -189,7 +188,7 @@ class RaceResult(db.Model):
if self._dnf_drivers is None:
self._dnf_drivers = dict()
for position, driver_name in self.dnf_driver_names.items():
driver = Driver.query.filter_by(name=driver_name).first()
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
if driver is None:
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
@ -200,13 +199,13 @@ class RaceResult(db.Model):
@property
def excluded_drivers(self) -> List[Driver]:
if self._excluded_drivers is None:
self._excluded_drivers = []
self._excluded_drivers = list()
for driver_name in self.excluded_driver_names:
driver = Driver.query.filter_by(name=driver_name).first()
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
if driver is None:
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
self._excluded_drivers += [driver]
self._excluded_drivers.append(driver)
return self._excluded_drivers
@ -230,14 +229,14 @@ class RaceGuess(db.Model):
__tablename__ = "raceguess"
__csv_header__ = ["user_name", "race_name", "pxx_driver_name", "dnf_driver_name"]
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.user_name = str(row[0])
self.race_name = str(row[1])
self.pxx_driver_name = str(row[2])
self.dnf_driver_name = str(row[3])
return self
def to_csv(self):
def to_csv(self) -> List[Any]:
return [
self.user_name,
self.race_name,
@ -265,12 +264,12 @@ class TeamWinners(db.Model):
__allow_unmapped__ = True
__csv_header__ = ["user_name", "teamwinner_driver_names_json"]
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.user_name = str(row[0])
self.teamwinner_driver_names_json = str(row[1])
return self
def to_csv(self):
def to_csv(self) -> List[Any]:
return [
self.user_name,
self.teamwinner_driver_names_json
@ -294,13 +293,13 @@ class TeamWinners(db.Model):
@property
def teamwinners(self) -> List[Driver]:
if self._teamwinner_drivers is None:
self._teamwinner_drivers = []
self._teamwinner_drivers = list()
for driver_name in self.teamwinner_driver_names:
driver = Driver.query.filter_by(name=driver_name).first()
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
if driver is None:
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
self._teamwinner_drivers += [driver]
self._teamwinner_drivers.append(driver)
return self._teamwinner_drivers
@ -313,12 +312,12 @@ class PodiumDrivers(db.Model):
__allow_unmapped__ = True
__csv_header__ = ["user_name", "podium_driver_names_json"]
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.user_name = str(row[0])
self.podium_driver_names_json = str(row[1])
return self
def to_csv(self):
def to_csv(self) -> List[Any]:
return [
self.user_name,
self.podium_driver_names_json
@ -342,13 +341,13 @@ class PodiumDrivers(db.Model):
@property
def podium_drivers(self) -> List[Driver]:
if self._podium_drivers is None:
self._podium_drivers = []
self._podium_drivers = list()
for driver_name in self.podium_driver_names:
driver = Driver.query.filter_by(name=driver_name).first()
driver: Driver | None = db.session.query(Driver).filter_by(name=driver_name).first()
if driver is None:
raise Exception(f"Error: Couldn't find driver with id {driver_name}")
self._podium_drivers += [driver]
self._podium_drivers.append(driver)
return self._podium_drivers
@ -362,7 +361,7 @@ class SeasonGuess(db.Model):
"overtake_driver_name", "dnf_driver_name", "gained_driver_name", "lost_driver_name",
"team_winners_id", "podium_drivers_id"]
def from_csv(self, row):
def from_csv(self, row: List[str]):
self.user_name = str(row[0]) # Also used as foreign key for teamwinners + podiumdrivers
self.hot_take = str(row[1])
self.p2_team_name = str(row[2])
@ -374,7 +373,7 @@ class SeasonGuess(db.Model):
self.podium_drivers_id = str(row[8])
return self
def to_csv(self):
def to_csv(self) -> List[Any]:
return [
self.user_name,
self.hot_take,

View File

@ -1,8 +1,6 @@
from typing import List, Iterable, Callable, TypeVar, Dict, overload, Any
from typing import List, Iterable, Callable, TypeVar, Dict, overload
from sqlalchemy import desc
from model import User, RaceResult, RaceGuess, Race, Driver, Team, SeasonGuess
from model import User, RaceResult, RaceGuess, Race, Driver, Team, SeasonGuess, db
_T = TypeVar("_T")
@ -73,7 +71,7 @@ class TemplateModel:
Returns a list of all users in the database.
"""
if self._all_users is None:
self._all_users = User.query.all()
self._all_users = db.session.query(User).all()
return self._all_users
@ -106,7 +104,7 @@ class TemplateModel:
Returns a list of all race results in the database, in descending order (most recent first).
"""
if self._all_race_results is None:
self._all_race_results = RaceResult.query.join(RaceResult.race).order_by(desc(Race.number)).all()
self._all_race_results = db.session.query(RaceResult).join(RaceResult.race).order_by(desc(Race.number)).all()
return self._all_race_results
@ -122,26 +120,26 @@ class TemplateModel:
Returns a list of all race guesses in the database.
"""
if self._all_race_guesses is None:
self._all_race_guesses = RaceGuess.query.all()
self._all_race_guesses = db.session.query(RaceGuess).all()
return self._all_race_guesses
@overload
def race_guesses_by(self, *, user_name) -> List[RaceGuess]:
def race_guesses_by(self, *, user_name: str) -> List[RaceGuess]:
"""
Returns a list of all race guesses made by a specific user.
"""
return self.race_guesses_by(user_name=user_name)
@overload
def race_guesses_by(self, *, race_name) -> List[RaceGuess]:
def race_guesses_by(self, *, race_name: str) -> List[RaceGuess]:
"""
Returns a list of all race guesses made for a specific race.
"""
return self.race_guesses_by(race_name=race_name)
@overload
def race_guesses_by(self, *, user_name, race_name) -> RaceGuess | None:
def race_guesses_by(self, *, user_name: str, race_name: str) -> RaceGuess | None:
"""
Returns a single race guess by a specific user for a specific race, or None, if this guess doesn't exist.
"""
@ -154,7 +152,7 @@ class TemplateModel:
"""
return self.race_guesses_by()
def race_guesses_by(self, *, user_name=None, race_name=None) -> RaceGuess | List[RaceGuess] | Dict[str, Dict[str, RaceGuess]] | None:
def race_guesses_by(self, *, user_name: str | None = None, race_name: str | None = None) -> RaceGuess | List[RaceGuess] | Dict[str, Dict[str, RaceGuess]] | None:
# List of all guesses by a single user
if user_name is not None and race_name is None:
predicate: Callable[[RaceGuess], bool] = lambda guess: guess.user_name == user_name
@ -187,12 +185,12 @@ class TemplateModel:
def all_season_guesses(self) -> List[SeasonGuess]:
if self._all_season_guesses is None:
self._all_season_guesses = SeasonGuess.query.all()
self._all_season_guesses = db.session.query(SeasonGuess).all()
return self._all_season_guesses
@overload
def season_guesses_by(self, *, user_name) -> SeasonGuess:
def season_guesses_by(self, *, user_name: str) -> SeasonGuess:
"""
Returns the season guess made by a specific user.
"""
@ -205,7 +203,7 @@ class TemplateModel:
"""
return self.season_guesses_by()
def season_guesses_by(self, *, user_name=None) -> SeasonGuess | Dict[str, SeasonGuess] | None:
def season_guesses_by(self, *, user_name: str | None = None) -> SeasonGuess | Dict[str, SeasonGuess] | None:
if user_name is not None:
predicate: Callable[[SeasonGuess], bool] = lambda guess: guess.user_name == user_name
return find_single_or_none(predicate, self.all_season_guesses())
@ -226,7 +224,7 @@ class TemplateModel:
Returns a list of all races in the database.
"""
if self._all_races is None:
self._all_races = Race.query.order_by(desc(Race.number)).all()
self._all_races = db.session.query(Race).order_by(desc(Race.number)).all()
return self._all_races
@ -248,7 +246,7 @@ class TemplateModel:
Returns a list of all teams in the database.
"""
if self._all_teams is None:
self._all_teams = Team.query.all()
self._all_teams = db.session.query(Team).all()
return self._all_teams
@ -257,7 +255,7 @@ class TemplateModel:
Returns a list of all drivers in the database, including the NONE driver.
"""
if self._all_drivers is None:
self._all_drivers = Driver.query.all()
self._all_drivers = db.session.query(Driver).all()
return self._all_drivers
@ -269,7 +267,7 @@ class TemplateModel:
return find_multiple(predicate, self.all_drivers())
@overload
def drivers_by(self, *, team_name) -> List[Driver]:
def drivers_by(self, *, team_name: str) -> List[Driver]:
"""
Returns a list of all drivers driving for a certain team.
"""
@ -282,7 +280,7 @@ class TemplateModel:
"""
return self.drivers_by()
def drivers_by(self, *, team_name=None) -> List[Driver] | Dict[str, List[Driver]]:
def drivers_by(self, *, team_name: str | None = None) -> List[Driver] | Dict[str, List[Driver]]:
if team_name is not None:
predicate: Callable[[Driver], bool] = lambda driver: driver.team.name == team_name
return find_multiple(predicate, self.all_drivers_except_none(), 2)

View File

@ -146,7 +146,7 @@ P{{ result.race.pxx + 3 }}: {{ result.pxx(3).abbr }}
<body>
<nav class="navbar fixed-top navbar-expand-lg bg-body-tertiary">
<nav class="navbar fixed-top navbar-expand-lg bg-body-tertiary shadow-sm">
<div class="container-fluid">
<a class="navbar-brand" href="/race">
<img src="../static/image/f1_logo.svg" alt="Logo" width="120" height="30"