diff --git a/app/routers/__init__.py b/app/routers/__init__.py index 8941d8c..1e34be5 100644 --- a/app/routers/__init__.py +++ b/app/routers/__init__.py @@ -1,6 +1,6 @@ from fastapi import APIRouter -from . import decks, games, players +from . import base, decks, games, players, seed api_router = APIRouter(prefix="/api") html_router = APIRouter() @@ -8,7 +8,11 @@ html_router = APIRouter() api_router.include_router(decks.api_router) api_router.include_router(players.api_router) api_router.include_router(games.api_router) +api_router.include_router(seed.api_router) html_router.include_router(decks.html_router) html_router.include_router(players.html_router) html_router.include_router(games.html_router) +html_router.include_router(seed.html_router) + +html_router.include_router(base.html_router) diff --git a/app/routers/base.py b/app/routers/base.py new file mode 100644 index 0000000..6a910a3 --- /dev/null +++ b/app/routers/base.py @@ -0,0 +1,16 @@ +from fastapi import APIRouter, Depends, Request +from fastapi.responses import HTMLResponse + +from ..sql import crud +from ..templates import jinja_templates, _jsonify +from ..sql.database import get_db + +html_router = APIRouter(include_in_schema=False, default_response_class=HTMLResponse) + + +@html_router.get("/") +def main(request: Request, db=Depends(get_db)): + games = crud.get_games(db=db) + return jinja_templates.TemplateResponse( + request, "/main.html", {"games": _jsonify(games)} + ) diff --git a/app/routers/seed.py b/app/routers/seed.py new file mode 100644 index 0000000..6a9ef31 --- /dev/null +++ b/app/routers/seed.py @@ -0,0 +1,53 @@ +import csv +import logging +from fastapi import APIRouter, Depends, Request, UploadFile +from fastapi.responses import HTMLResponse +from sqlalchemy.orm import Session + +from ..templates import jinja_templates +from ..sql import crud, schemas +from ..sql.database import get_db + + +LOGGER = logging.getLogger(__name__) + +api_router = APIRouter(prefix="/seed", tags=["seed"]) +html_router = APIRouter( + prefix="/seed", include_in_schema=False, default_response_class=HTMLResponse +) + + +@api_router.post("/players") +def seed_players(file: UploadFile, db: Session = Depends(get_db)): + file_contents = file.file.read().decode("utf-8").split("\n") + reader = csv.reader(file_contents, delimiter=",") + for row in reader: + if not row: + continue + player_name = row[1] + crud.create_player(db=db, player=schemas.PlayerCreate(name=player_name)) + return "OK!" + + +@api_router.post("/decks") +def seed_decks(file: UploadFile, db: Session = Depends(get_db)): + file_contents = file.file.read().decode("utf-8").split("\n") + reader = csv.DictReader(file_contents, delimiter=",") + for row in reader: + if not row: + continue + crud.create_deck( + db=db, + deck=schemas.DeckCreate( + **{key: row[key] for key in ["name", "description", "owner_id"]} + ), + ) + return "OK!" + + +@html_router.get("/") +def main(request: Request, db=Depends(get_db)): + return jinja_templates.TemplateResponse( + request, + "/seed.html", + ) diff --git a/app/sql/schemas.py b/app/sql/schemas.py index b2fd237..3f3d3d2 100644 --- a/app/sql/schemas.py +++ b/app/sql/schemas.py @@ -1,3 +1,4 @@ +from datetime import datetime from typing import Optional from pydantic import BaseModel @@ -47,7 +48,7 @@ class WinType(WinTypeBase): class GameBase(BaseModel): - date: int + date: datetime deck_id_1: int deck_id_2: int deck_id_3: int diff --git a/app/templates/__init__.py b/app/templates/__init__.py index 60a5a45..40bb4bd 100644 --- a/app/templates/__init__.py +++ b/app/templates/__init__.py @@ -13,5 +13,10 @@ jinja_templates = Jinja2Templates(directory="app/templates") # TODO - would this be better as a method on a class extending `db.Model` that the classes in `models.py` could then # extend? # (Probably not, as we'd still need to explicitly call it - it wouldn't be implicitly called _by_ Flask) +# +# (Assumes that this will only be passed lists or objects, not primitives) def _jsonify(o): - return {k: v for (k, v) in o.__dict__.items() if k != "_sa_instance_state"} + if hasattr(o, "__dict__"): + return {k: v for (k, v) in o.__dict__.items() if k != "_sa_instance_state"} + else: + return [_jsonify(e) for e in o] diff --git a/app/templates/games/list.html b/app/templates/games/list.html index ca05137..57ccb4e 100644 --- a/app/templates/games/list.html +++ b/app/templates/games/list.html @@ -9,11 +9,12 @@ - + + {% for game in games %} - + diff --git a/app/templates/main.html b/app/templates/main.html new file mode 100644 index 0000000..521d08a --- /dev/null +++ b/app/templates/main.html @@ -0,0 +1,10 @@ +{% extends "base.html" %} + +{% block title %}EDH ELO{% endblock %} + +{% block head %} +{% endblock %} + +{% block content %} +

Welcome to EDH ELO!

+{% endblock %} \ No newline at end of file diff --git a/app/templates/seed.html b/app/templates/seed.html new file mode 100644 index 0000000..64426fa --- /dev/null +++ b/app/templates/seed.html @@ -0,0 +1,27 @@ +{% extends "base.html" %} + +{% block title %}Seeding from files{% endblock %} + +{% block head %} + +{% endblock %} + +{% block content %} +

Seed from files

+ +
+
+ + + Upload + +
+ +
+
+ + + Upload + +
+{% endblock %} \ No newline at end of file diff --git a/basic-run.sh b/basic-run.sh index 5afab49..5dae1fc 100755 --- a/basic-run.sh +++ b/basic-run.sh @@ -2,4 +2,4 @@ # Idempotent source .venv/bin/activate -uvicorn app:app --reload +uvicorn app:app --reload --log-config ./local-run-log-config.yaml diff --git a/local-run-log-config.yaml b/local-run-log-config.yaml new file mode 100644 index 0000000..100f668 --- /dev/null +++ b/local-run-log-config.yaml @@ -0,0 +1,37 @@ +# See https://github.com/encode/uvicorn/discussions/2254 - +# Ideally, would not have to re-implement uvicorn's loggers. +version: 1 +disable_existing_loggers: False +formatters: + default: + (): 'uvicorn.logging.DefaultFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(message)s' + access: + (): 'uvicorn.logging.AccessFormatter' + fmt: '%(asctime)s %(levelprefix)-9s %(name)s -: %(client_addr)s - "%(request_line)s" %(status_code)s' +handlers: + default: + class: logging.StreamHandler + formatter: default + stream: ext://sys.stderr + access: + class: logging.StreamHandler + formatter: access + stream: ext://sys.stdout +loggers: + uvicorn: + level: INFO + propagate: False + handlers: + - default + uvicorn.error: + level: INFO + uvicorn.access: + level: INFO + propagate: False + handlers: + - access +root: + level: INFO + handlers: + - default diff --git a/requirements.txt b/requirements.txt index 0816db8..86e3ebd 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,6 @@ fastapi -uvicorn +python-multipart sqlalchemy +uvicorn Jinja2 +pyyaml diff --git a/seed-data/README.md b/seed-data/README.md new file mode 100644 index 0000000..72fd0c6 --- /dev/null +++ b/seed-data/README.md @@ -0,0 +1 @@ +Data I use for initialization during testing. \ No newline at end of file diff --git a/seed-data/decks.csv b/seed-data/decks.csv new file mode 100644 index 0000000..c7f5740 --- /dev/null +++ b/seed-data/decks.csv @@ -0,0 +1,57 @@ +id,name,description,owner_id +1,Kelsien the Plague,,1 +2,Ravos/Rebbec,,2 +3,Duke Ulder Ravengard,,3 +4,Mondrak,,4 +5,Wernog/Cecily,,2 +6,Jasmine Boreal of the Seven,,1 +7,Go-Shintai of Life's Origin,,4 +8,Kethis the Hidden Hand,,1 +9,Rograkh/Silas ninjas,,2 +10,Tekuthal,,3 +11,Gitrog,,5 +12,Illuna,,4 +13,Atraxa,,1 +14,Muldrotha,,2 +15,Grist,,5 +16,Goose Mother,,5 +17,Maarika,,3 +18,Wilson/Cultist,,2 +19,Abdel Adrian/Far Traveler,,2 +20,Obeka,,5 +21,Laelia,,3 +22,Jan Jansen,,2 +23,Don Andres,,5 +24,Urza,,3 +25,"Me,the Immortal",,2 +26,Kiora,,3 +27,Raffine,,2 +28,Kozilek,,5 +29,Jhoira of the Ghitu,,6 +30,Anikthea,,7 +31,Oops all Kayas,,3 +32,Silvar/Trynn,,7 +33,"Purphoros, God of the Forge",,5 +34,Jhoira of the Ghitu,,6 +35,Brago,,6 +36,Marneus Calgar,,7 +37,Slimefoot and Squee,,2 +38,Ayara,,3 +39,Omnath,,5 +40,Wilson/Cultist,,2 +41,Myrel,,5 +42,Elmar storm,,3 +43,Gale/Scion of Halaster,,8 +44,Kefnet the Mindful,,2 +45,Rograkh/Silas ninjas,,2 +46,Emiel,,5 +47,Myrkul planeswalkers,,3 +48,Syr Ginger,,8 +49,Rafiq,,3 +50,Yoshimaru/Reyhan,,2 +51,Go-Shintai of Life's Origin,,4 +52,Dargo/Nadier,,2 +53,Pantlaza,,5 +54,Niv-Mizzet,,3 +55,"Liesa, Shroud of Dusk",,5 +56,Malcolm/Ich-Tekik,,2 diff --git a/seed-data/players.csv b/seed-data/players.csv new file mode 100644 index 0000000..c012297 --- /dev/null +++ b/seed-data/players.csv @@ -0,0 +1,8 @@ +1,Evan +2,Terence +3,Patrick +4,Jeff +5,Ryan +6,Ajit +7,Brandon +8,Jack
DateDeck ID 1DecksWinning Deck
{{ game.date }}{{ game.date }} {{ game.deck_id_1 }}