Initial seeding logic

This commit is contained in:
Jack Jackson 2024-03-01 21:35:06 -08:00
parent c36c4bd3b8
commit cc6c4818ad
14 changed files with 229 additions and 7 deletions

View File

@ -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)

16
app/routers/base.py Normal file
View File

@ -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)}
)

53
app/routers/seed.py Normal file
View File

@ -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",
)

View File

@ -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

View File

@ -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):
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]

View File

@ -9,11 +9,12 @@
<table>
<tr>
<th>Date</th>
<th>Deck ID 1</th>
<th>Decks</th>
<th>Winning Deck</th>
</tr>
{% for game in games %}
<tr>
<td>{{ game.date }}</td>
<td><a href="/game/{{ game.id }}">{{ game.date }}</a></td>
<td>
{{ game.deck_id_1 }}
</td>

10
app/templates/main.html Normal file
View File

@ -0,0 +1,10 @@
{% extends "base.html" %}
{% block title %}EDH ELO{% endblock %}
{% block head %}
{% endblock %}
{% block content %}
<p>Welcome to EDH ELO!</p>
{% endblock %}

27
app/templates/seed.html Normal file
View File

@ -0,0 +1,27 @@
{% extends "base.html" %}
{% block title %}Seeding from files{% endblock %}
{% block head %}
{% endblock %}
{% block content %}
<h2>Seed from files</h2>
<div>
<form action="/api/seed/players" method="post" enctype="multipart/form-data">
<label for="file">Upload Players</label>
<input type="file" id="file" name="file" accept=".csv"/>
<input type="submit">Upload</button>
</form>
</div>
<div>
<form action="/api/seed/decks" method="post" enctype="multipart/form-data">
<label for="file">Upload Decks</label>
<input type="file" id="file" name="file" accept=".csv"/>
<input type="submit">Upload</button>
</form>
</div>
{% endblock %}

View File

@ -2,4 +2,4 @@
# Idempotent
source .venv/bin/activate
uvicorn app:app --reload
uvicorn app:app --reload --log-config ./local-run-log-config.yaml

37
local-run-log-config.yaml Normal file
View File

@ -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

View File

@ -1,4 +1,6 @@
fastapi
uvicorn
python-multipart
sqlalchemy
uvicorn
Jinja2
pyyaml

1
seed-data/README.md Normal file
View File

@ -0,0 +1 @@
Data I use for initialization during testing.

57
seed-data/decks.csv Normal file
View File

@ -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
1 id name description owner_id
2 1 Kelsien the Plague 1
3 2 Ravos/Rebbec 2
4 3 Duke Ulder Ravengard 3
5 4 Mondrak 4
6 5 Wernog/Cecily 2
7 6 Jasmine Boreal of the Seven 1
8 7 Go-Shintai of Life's Origin 4
9 8 Kethis the Hidden Hand 1
10 9 Rograkh/Silas ninjas 2
11 10 Tekuthal 3
12 11 Gitrog 5
13 12 Illuna 4
14 13 Atraxa 1
15 14 Muldrotha 2
16 15 Grist 5
17 16 Goose Mother 5
18 17 Maarika 3
19 18 Wilson/Cultist 2
20 19 Abdel Adrian/Far Traveler 2
21 20 Obeka 5
22 21 Laelia 3
23 22 Jan Jansen 2
24 23 Don Andres 5
25 24 Urza 3
26 25 Me,the Immortal 2
27 26 Kiora 3
28 27 Raffine 2
29 28 Kozilek 5
30 29 Jhoira of the Ghitu 6
31 30 Anikthea 7
32 31 Oops all Kayas 3
33 32 Silvar/Trynn 7
34 33 Purphoros, God of the Forge 5
35 34 Jhoira of the Ghitu 6
36 35 Brago 6
37 36 Marneus Calgar 7
38 37 Slimefoot and Squee 2
39 38 Ayara 3
40 39 Omnath 5
41 40 Wilson/Cultist 2
42 41 Myrel 5
43 42 Elmar storm 3
44 43 Gale/Scion of Halaster 8
45 44 Kefnet the Mindful 2
46 45 Rograkh/Silas ninjas 2
47 46 Emiel 5
48 47 Myrkul planeswalkers 3
49 48 Syr Ginger 8
50 49 Rafiq 3
51 50 Yoshimaru/Reyhan 2
52 51 Go-Shintai of Life's Origin 4
53 52 Dargo/Nadier 2
54 53 Pantlaza 5
55 54 Niv-Mizzet 3
56 55 Liesa, Shroud of Dusk 5
57 56 Malcolm/Ich-Tekik 2

8
seed-data/players.csv Normal file
View File

@ -0,0 +1,8 @@
1,Evan
2,Terence
3,Patrick
4,Jeff
5,Ryan
6,Ajit
7,Brandon
8,Jack
1 1 Evan
2 2 Terence
3 3 Patrick
4 4 Jeff
5 5 Ryan
6 6 Ajit
7 7 Brandon
8 8 Jack