Initial seeding logic
This commit is contained in:
parent
c36c4bd3b8
commit
cc6c4818ad
@ -1,6 +1,6 @@
|
|||||||
from fastapi import APIRouter
|
from fastapi import APIRouter
|
||||||
|
|
||||||
from . import decks, games, players
|
from . import base, decks, games, players, seed
|
||||||
|
|
||||||
api_router = APIRouter(prefix="/api")
|
api_router = APIRouter(prefix="/api")
|
||||||
html_router = APIRouter()
|
html_router = APIRouter()
|
||||||
@ -8,7 +8,11 @@ html_router = APIRouter()
|
|||||||
api_router.include_router(decks.api_router)
|
api_router.include_router(decks.api_router)
|
||||||
api_router.include_router(players.api_router)
|
api_router.include_router(players.api_router)
|
||||||
api_router.include_router(games.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(decks.html_router)
|
||||||
html_router.include_router(players.html_router)
|
html_router.include_router(players.html_router)
|
||||||
html_router.include_router(games.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
16
app/routers/base.py
Normal 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
53
app/routers/seed.py
Normal 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",
|
||||||
|
)
|
@ -1,3 +1,4 @@
|
|||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel
|
from pydantic import BaseModel
|
||||||
|
|
||||||
@ -47,7 +48,7 @@ class WinType(WinTypeBase):
|
|||||||
|
|
||||||
|
|
||||||
class GameBase(BaseModel):
|
class GameBase(BaseModel):
|
||||||
date: int
|
date: datetime
|
||||||
deck_id_1: int
|
deck_id_1: int
|
||||||
deck_id_2: int
|
deck_id_2: int
|
||||||
deck_id_3: int
|
deck_id_3: int
|
||||||
|
@ -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
|
# TODO - would this be better as a method on a class extending `db.Model` that the classes in `models.py` could then
|
||||||
# extend?
|
# extend?
|
||||||
# (Probably not, as we'd still need to explicitly call it - it wouldn't be implicitly called _by_ Flask)
|
# (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):
|
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]
|
||||||
|
@ -9,11 +9,12 @@
|
|||||||
<table>
|
<table>
|
||||||
<tr>
|
<tr>
|
||||||
<th>Date</th>
|
<th>Date</th>
|
||||||
<th>Deck ID 1</th>
|
<th>Decks</th>
|
||||||
|
<th>Winning Deck</th>
|
||||||
</tr>
|
</tr>
|
||||||
{% for game in games %}
|
{% for game in games %}
|
||||||
<tr>
|
<tr>
|
||||||
<td>{{ game.date }}</td>
|
<td><a href="/game/{{ game.id }}">{{ game.date }}</a></td>
|
||||||
<td>
|
<td>
|
||||||
{{ game.deck_id_1 }}
|
{{ game.deck_id_1 }}
|
||||||
</td>
|
</td>
|
||||||
|
10
app/templates/main.html
Normal file
10
app/templates/main.html
Normal 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
27
app/templates/seed.html
Normal 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 %}
|
@ -2,4 +2,4 @@
|
|||||||
|
|
||||||
# Idempotent
|
# Idempotent
|
||||||
source .venv/bin/activate
|
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
37
local-run-log-config.yaml
Normal 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
|
@ -1,4 +1,6 @@
|
|||||||
fastapi
|
fastapi
|
||||||
uvicorn
|
python-multipart
|
||||||
sqlalchemy
|
sqlalchemy
|
||||||
|
uvicorn
|
||||||
Jinja2
|
Jinja2
|
||||||
|
pyyaml
|
||||||
|
1
seed-data/README.md
Normal file
1
seed-data/README.md
Normal file
@ -0,0 +1 @@
|
|||||||
|
Data I use for initialization during testing.
|
57
seed-data/decks.csv
Normal file
57
seed-data/decks.csv
Normal 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
|
|
8
seed-data/players.csv
Normal file
8
seed-data/players.csv
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
1,Evan
|
||||||
|
2,Terence
|
||||||
|
3,Patrick
|
||||||
|
4,Jeff
|
||||||
|
5,Ryan
|
||||||
|
6,Ajit
|
||||||
|
7,Brandon
|
||||||
|
8,Jack
|
|
Loading…
x
Reference in New Issue
Block a user