Merge pull request #3 from calcorum/v2-refactor

V2 refactor
This commit is contained in:
Cal Corum 2023-10-24 10:19:39 -05:00 committed by GitHub
commit 92ba5129a1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
41 changed files with 13446 additions and 209 deletions

0
app/__init__.py Normal file
View File

3079
app/card_creation.py Normal file

File diff suppressed because one or more lines are too long

1005
app/db_engine.py Normal file

File diff suppressed because it is too large Load Diff

195
app/dependencies.py Normal file
View File

@ -0,0 +1,195 @@
import datetime
import logging
import os
import requests
from fastapi.security import OAuth2PasswordBearer
date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
LOG_DATA = {
'filename': f'logs/database/{date}.log',
'format': '%(asctime)s - database - %(levelname)s - %(message)s',
'log_level': logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
}
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
master_debug = False
DB_URL = 'https://pd.manticorum.com/api/'
AUTH_TOKEN = f'{os.environ.get("API_TOKEN")}'
AUTH_HEADER = {'Authorization': f'Bearer {AUTH_TOKEN}'}
if os.environ.get('TESTING') == 'False':
DB_URL = 'https://pddev.manticorum.com/api/'
def valid_token(token):
return token == AUTH_TOKEN
def int_timestamp(datetime_obj: datetime) -> int:
return int(datetime.datetime.timestamp(datetime_obj) * 1000)
def mround(x, prec=2, base=.05):
return round(base * round(float(x) / base), prec)
def param_char(other_params):
if other_params:
return '&'
else:
return '?'
def get_req_url(endpoint: str, api_ver: int = 2, object_id: int = None, params: list = None):
req_url = f'{DB_URL}/v{api_ver}/{endpoint}{"/" if object_id is not None else ""}{object_id if object_id is not None else ""}'
if params:
other_params = False
for x in params:
req_url += f'{param_char(other_params)}{x[0]}={x[1]}'
other_params = True
return req_url
async def db_get(endpoint: str, api_ver: int = 2, object_id: int = None, params: list = None, none_okay: bool = True,
timeout: int = 3):
req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id, params=params)
log_string = f'get:\n{endpoint} id: {object_id} params: {params}'
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
while True:
try:
resp = requests.get(req_url, timeout=timeout)
break
except requests.ReadTimeout as e:
logging.error(f'Get Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
timeout += [2, 5][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
return data
elif none_okay:
data = resp.json()
log_string = f'{data}'
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
return None
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')
async def db_patch(endpoint: str, object_id: int, params: list, api_ver: int = 2, timeout: int = 3):
req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id, params=params)
log_string = f'patch:\n{endpoint} {params}'
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
while True:
try:
resp = requests.patch(req_url, headers=AUTH_HEADER, timeout=timeout)
break
except requests.Timeout as e:
logging.error(f'Patch Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
timeout += [min(3, timeout), min(5, timeout)][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
return data
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')
async def db_post(endpoint: str, api_ver: int = 2, payload: dict = None, timeout: int = 3):
req_url = get_req_url(endpoint, api_ver=api_ver)
log_string = f'post:\n{endpoint} payload: {payload}\ntype: {type(payload)}'
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
while True:
try:
resp = requests.post(req_url, json=payload, headers=AUTH_HEADER, timeout=timeout)
break
except requests.Timeout as e:
logging.error(f'Post Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
timeout += [min(3, timeout), min(5, timeout)][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
return data
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')
async def db_delete(endpoint: str, object_id: int, api_ver: int = 2, timeout=3):
req_url = get_req_url(endpoint, api_ver=api_ver, object_id=object_id)
log_string = f'delete:\n{endpoint} {object_id}'
logging.info(log_string) if master_debug else logging.debug(log_string)
retries = 0
while True:
try:
resp = requests.delete(req_url, headers=AUTH_HEADER, timeout=timeout)
break
except requests.ReadTimeout as e:
logging.error(f'Delete Timeout: {req_url} / retries: {retries} / timeout: {timeout}')
if retries > 1:
raise ConnectionError(f'DB: The internet was a bit too slow for me to grab the data I needed. Please '
f'hang on a few extra seconds and try again.')
timeout += [min(3, timeout), min(5, timeout)][retries]
retries += 1
if resp.status_code == 200:
data = resp.json()
log_string = f'{data}'
if master_debug:
logging.info(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
else:
logging.debug(f'return: {log_string[:1200]}{" [ S N I P P E D ]" if len(log_string) > 1200 else ""}')
return True
else:
logging.warning(resp.text)
raise ValueError(f'DB: {resp.text}')

46
app/main.py Normal file
View File

@ -0,0 +1,46 @@
import os
from fastapi import FastAPI
# from fastapi.staticfiles import StaticFiles
# from fastapi.templating import Jinja2Templates
from .routers_v2 import (
current, teams, rarity, cardsets, players, packtypes, packs, cards, events, results, rewards, decisions,
batstats, pitstats, notifications, paperdex, gamerewards, gauntletrewards, gauntletruns, battingcards,
battingcardratings, pitchingcards, pitchingcardratings, cardpositions, scouting, mlbplayers, stratgame, stratplays)
app = FastAPI(
responses={404: {'description': 'Not found'}}
)
# app.mount("/static", StaticFiles(directory="storage/static"), name="static")
# templates = Jinja2Templates(directory=os.path.dirname(os.path.abspath(__file__)))
app.include_router(current.router)
app.include_router(teams.router)
app.include_router(rarity.router)
app.include_router(cardsets.router)
app.include_router(players.router)
app.include_router(packtypes.router)
app.include_router(packs.router)
app.include_router(cards.router)
app.include_router(events.router)
app.include_router(results.router)
app.include_router(rewards.router)
app.include_router(batstats.router)
app.include_router(pitstats.router)
app.include_router(notifications.router)
app.include_router(paperdex.router)
app.include_router(gamerewards.router)
app.include_router(gauntletrewards.router)
app.include_router(gauntletruns.router)
app.include_router(battingcards.router)
app.include_router(battingcardratings.router)
app.include_router(pitchingcards.router)
app.include_router(pitchingcardratings.router)
app.include_router(cardpositions.router)
app.include_router(scouting.router)
app.include_router(mlbplayers.router)
app.include_router(stratgame.router)
app.include_router(stratplays.router)
app.include_router(decisions.router)

27
app/player_scouting.py Normal file
View File

@ -0,0 +1,27 @@
from typing import Literal, Optional
from pybaseball import playerid_reverse_lookup
import pydantic
class PlayerIds(pydantic.BaseModel):
bbref: str = None
fangraphs: int = None
retro: str = None
mlbam: int = None
def get_player_ids(player_id: str, id_type: Literal['bbref', 'fangraphs']) -> PlayerIds | None:
q = playerid_reverse_lookup([player_id], key_type=id_type)
if len(q.values) == 0:
return None
else:
return PlayerIds(
bbref=q.loc[0].key_bbref,
fangraphs=q.loc[0].key_fangraphs,
retro=q.loc[0].key_retro,
mlbam=q.loc[0].key_mlbam
)

View File

35
app/routers_v2/admin.py Normal file
View File

@ -0,0 +1,35 @@
from fastapi import APIRouter, Depends, HTTPException
import logging
from ..db_engine import db, Player
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/admin',
tags=['admin']
)
@router.post('/stl-fix')
async def stl_cardinals_fix(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post. This event has been logged.'
)
p_query = Player.update(mlbclub='St Louis Cardinals', franchise='St Louis Cardinals').where(
Player.mlbclub == 'St. Louis Cardinals'
).execute()
db.close()
return {'detail': f'Removed the period from St Louis'}

155
app/routers_v2/awards.py Normal file
View File

@ -0,0 +1,155 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Award, model_to_dict
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/awards',
tags=['awards']
)
class AwardModel(pydantic.BaseModel):
name: str
season: int
timing: str = 'In-Season'
card_id: Optional[int] = None
team_id: Optional[int] = None
image: Optional[str] = None
@app.get('/api/v1/awards')
async def get_awards(
name: Optional[str] = None, season: Optional[int] = None, timing: Optional[str] = None,
card_id: Optional[int] = None, team_id: Optional[int] = None, image: Optional[str] = None,
csv: Optional[bool] = None):
all_awards = Award.select()
if all_awards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no awards to filter')
if name is not None:
all_awards = all_awards.where(Award.name == name)
if season is not None:
all_awards = all_awards.where(Award.season == season)
if timing is not None:
all_awards = all_awards.where(Award.timing == timing)
if card_id is not None:
all_awards = all_awards.where(Award.card_id == card_id)
if team_id is not None:
all_awards = all_awards.where(Award.team_id == team_id)
if image is not None:
all_awards = all_awards.where(Award.image == image)
if csv:
data_list = [['id', 'name', 'season', 'timing', 'card', 'team', 'image']]
for line in all_awards:
data_list.append([
line.id, line.name, line.season, line.timing, line.card, line.team, line.image
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_awards.count(), 'awards': []}
for x in all_awards:
return_val['awards'].append(model_to_dict(x))
db.close()
return return_val
@app.get('/api/v1/awards/{award_id}')
async def get_one_award(award_id, csv: Optional[bool] = None):
try:
this_award = Award.get_by_id(award_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No award found with id {award_id}')
if csv:
data_list = [
['id', 'name', 'season', 'timing', 'card', 'team', 'image'],
[this_award.id, this_award.name, this_award.season, this_award.timing, this_award.card,
this_award.team, this_award.image]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_award)
db.close()
return return_val
@app.post('/api/v1/awards')
async def post_awards(award: AwardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post awards. This event has been logged.'
)
this_award = Award(
name=award.name,
season=award.season,
timing=award.season,
card_id=award.card_id,
team_id=award.team_id,
image=award.image
)
saved = this_award.save()
if saved == 1:
return_val = model_to_dict(this_award)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that roster'
)
@app.delete('/api/v1/awards/{award_id}')
async def delete_award(award_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete awards. This event has been logged.'
)
try:
this_award = Award.get_by_id(award_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No award found with id {award_id}')
count = this_award.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Award {award_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Award {award_id} was not deleted')

459
app/routers_v2/batstats.py Normal file
View File

@ -0,0 +1,459 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional, List
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, BattingStat, model_to_dict, fn, Card, Player, Current
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/batstats',
tags=['batstats']
)
class BatStat(pydantic.BaseModel):
card_id: int
team_id: int
roster_num: int
vs_team_id: int
pos: str
pa: Optional[int] = 0
ab: Optional[int] = 0
run: Optional[int] = 0
hit: Optional[int] = 0
rbi: Optional[int] = 0
double: Optional[int] = 0
triple: Optional[int] = 0
hr: Optional[int] = 0
bb: Optional[int] = 0
so: Optional[int] = 0
hbp: Optional[int] = 0
sac: Optional[int] = 0
ibb: Optional[int] = 0
gidp: Optional[int] = 0
sb: Optional[int] = 0
cs: Optional[int] = 0
bphr: Optional[int] = 0
bpfo: Optional[int] = 0
bp1b: Optional[int] = 0
bplo: Optional[int] = 0
xch: Optional[int] = 0
xhit: Optional[int] = 0
error: Optional[int] = 0
pb: Optional[int] = 0
sbc: Optional[int] = 0
csc: Optional[int] = 0
week: int
season: int
created: Optional[int] = int(datetime.timestamp(datetime.now())*100000)
game_id: int
class BattingStatModel(pydantic.BaseModel):
stats: List[BatStat]
@router.get('')
async def get_batstats(
card_id: int = None, player_id: int = None, team_id: int = None, vs_team_id: int = None, week: int = None,
season: int = None, week_start: int = None, week_end: int = None, created: int = None, csv: bool = None):
all_stats = BattingStat.select().join(Card).join(Player)
if season is not None:
all_stats = all_stats.where(BattingStat.season == season)
else:
curr = Current.latest()
all_stats = all_stats.where(BattingStat.season == curr.season)
if card_id is not None:
all_stats = all_stats.where(BattingStat.card_id == card_id)
if player_id is not None:
all_stats = all_stats.where(BattingStat.card.player.player_id == player_id)
if team_id is not None:
all_stats = all_stats.where(BattingStat.team_id == team_id)
if vs_team_id is not None:
all_stats = all_stats.where(BattingStat.vs_team_id == vs_team_id)
if week is not None:
all_stats = all_stats.where(BattingStat.week == week)
if week_start is not None:
all_stats = all_stats.where(BattingStat.week >= week_start)
if week_end is not None:
all_stats = all_stats.where(BattingStat.week <= week_end)
if created is not None:
all_stats = all_stats.where(BattingStat.created == created)
# if all_stats.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No batting stats found')
if csv:
data_list = [['id', 'card_id', 'player_id', 'cardset', 'team', 'vs_team', 'pos', 'pa', 'ab', 'run', 'hit', 'rbi', 'double',
'triple', 'hr', 'bb', 'so', 'hbp', 'sac', 'ibb', 'gidp', 'sb', 'cs', 'bphr', 'bpfo', 'bp1b',
'bplo', 'xch', 'xhit', 'error', 'pb', 'sbc', 'csc', 'week', 'season', 'created', 'game_id', 'roster_num']]
for line in all_stats:
data_list.append(
[
line.id, line.card.id, line.card.player.player_id, line.card.player.cardset.name, line.team.abbrev, line.vs_team.abbrev,
line.pos, line.pa, line.ab, line.run, line.hit, line.rbi, line.double, line.triple, line.hr,
line.bb, line.so, line.hbp, line.sac, line.ibb, line.gidp, line.sb, line.cs, line.bphr, line.bpfo,
line.bp1b, line.bplo, line.xch, line.xhit, line.error, line.pb, line.sbc, line.csc, line.week,
line.season, line.created, line.game_id, line.roster_num
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_stats.count(), 'stats': []}
for x in all_stats:
return_val['stats'].append(model_to_dict(x, recurse=False))
db.close()
return return_val
@router.get('/player/{player_id}')
async def get_player_stats(
player_id: int, team_id: int = None, vs_team_id: int = None, week_start: int = None, week_end: int = None,
csv: bool = None):
all_stats = (BattingStat
.select(fn.COUNT(BattingStat.created).alias('game_count'))
.join(Card)
.group_by(BattingStat.card)
.where(BattingStat.card.player == player_id)).scalar()
if team_id is not None:
all_stats = all_stats.where(BattingStat.team_id == team_id)
if vs_team_id is not None:
all_stats = all_stats.where(BattingStat.vs_team_id == vs_team_id)
if week_start is not None:
all_stats = all_stats.where(BattingStat.week >= week_start)
if week_end is not None:
all_stats = all_stats.where(BattingStat.week <= week_end)
if csv:
data_list = [
[
'pa', 'ab', 'run', 'hit', 'rbi', 'double', 'triple', 'hr', 'bb', 'so', 'hbp', 'sac', 'ibb', 'gidp',
'sb', 'cs', 'bphr', 'bpfo', 'bp1b', 'bplo', 'xch', 'xhit', 'error', 'pb', 'sbc', 'csc',
],[
all_stats.pa_sum, all_stats.ab_sum, all_stats.run, all_stats.hit_sum, all_stats.rbi_sum,
all_stats.double_sum, all_stats.triple_sum, all_stats.hr_sum, all_stats.bb_sum, all_stats.so_sum,
all_stats.hbp_sum, all_stats.sac, all_stats.ibb_sum, all_stats.gidp_sum, all_stats.sb_sum,
all_stats.cs_sum, all_stats.bphr_sum, all_stats.bpfo_sum, all_stats.bp1b_sum, all_stats.bplo_sum,
all_stats.xch, all_stats.xhit_sum, all_stats.error_sum, all_stats.pb_sum, all_stats.sbc_sum,
all_stats.csc_sum
]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
logging.debug(f'stat pull query: {all_stats}\n')
# logging.debug(f'result 0: {all_stats[0]}\n')
for x in all_stats:
logging.debug(f'this_line: {model_to_dict(x)}')
return_val = model_to_dict(all_stats[0])
db.close()
return return_val
@router.post('')
async def post_batstats(stats: BattingStatModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post stats. This event has been logged.'
)
new_stats = []
for x in stats.stats:
this_stat = BattingStat(
card_id=x.card_id,
team_id=x.team_id,
roster_num=x.roster_num,
vs_team_id=x.vs_team_id,
pos=x.pos,
pa=x.pa,
ab=x.ab,
run=x.run,
hit=x.hit,
rbi=x.rbi,
double=x.double,
triple=x.triple,
hr=x.hr,
bb=x.bb,
so=x.so,
hbp=x.hbp,
sac=x.sac,
ibb=x.ibb,
gidp=x.gidp,
sb=x.sb,
cs=x.cs,
bphr=x.bphr,
bpfo=x.bpfo,
bp1b=x.bp1b,
bplo=x.bplo,
xch=x.xch,
xhit=x.xhit,
error=x.error,
pb=x.pb,
sbc=x.sbc,
csc=x.csc,
week=x.week,
season=x.season,
created=x.created,
game_id=x.game_id
)
new_stats.append(this_stat)
with db.atomic():
BattingStat.bulk_create(new_stats, batch_size=15)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_stats)} batting lines have been added')
@router.delete('/{stat_id}')
async def delete_batstat(stat_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete stats. This event has been logged.'
)
try:
this_stat = BattingStat.get_by_id(stat_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No stat found with id {stat_id}')
count = this_stat.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Stat {stat_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Stat {stat_id} was not deleted')
# @app.get('/api/v1/plays/batting')
# async def get_batting_totals(
# player_id: list = Query(default=None), team_id: list = Query(default=None), min_pa: Optional[int] = 1,
# season: list = Query(default=None), position: list = Query(default=None),
# group_by: Literal['team', 'player', 'playerteam', 'playergame', 'teamgame', 'league'] = 'player',
# sort: Optional[str] = None, limit: Optional[int] = None, short_output: Optional[bool] = False):
# all_stats = BattingStat.select(
# BattingStat.card, BattingStat.game_id, BattingStat.team, BattingStat.vs_team, BattingStat.pos,
# BattingStat.card.player.alias('player'),
# fn.SUM(BattingStat.pa).alias('sum_pa'), fn.SUM(BattingStat.ab).alias('sum_ab'),
# fn.SUM(BattingStat.run).alias('sum_run'), fn.SUM(BattingStat.so).alias('sum_so'),
# fn.SUM(BattingStat.hit).alias('sum_hit'), fn.SUM(BattingStat.rbi).alias('sum_rbi'),
# fn.SUM(BattingStat.double).alias('sum_double'), fn.SUM(BattingStat.triple).alias('sum_triple'),
# fn.SUM(BattingStat.hr).alias('sum_hr'), fn.SUM(BattingStat.bb).alias('sum_bb'),
# fn.SUM(BattingStat.hbp).alias('sum_hbp'), fn.SUM(BattingStat.sac).alias('sum_sac'),
# fn.SUM(BattingStat.ibb).alias('sum_ibb'), fn.SUM(BattingStat.gidp).alias('sum_gidp'),
# fn.SUM(BattingStat.sb).alias('sum_sb'), fn.SUM(BattingStat.cs).alias('sum_cs'),
# fn.SUM(BattingStat.bphr).alias('sum_bphr'), fn.SUM(BattingStat.bpfo).alias('sum_bpfo'),
# fn.SUM(BattingStat.bp1b).alias('sum_bp1b'), fn.SUM(BattingStat.bplo).alias('sum_bplo')
# ).having(
# fn.SUM(BattingStat.pa) >= min_pa
# ).join(Card)
#
# if player_id is not None:
# # all_players = Player.select().where(Player.id << player_id)
# all_cards = Card.select().where(Card.player_id << player_id)
# all_stats = all_stats.where(BattingStat.card << all_cards)
# if team_id is not None:
# all_teams = Team.select().where(Team.id << team_id)
# all_stats = all_stats.where(BattingStat.team << all_teams)
# if season is not None:
# all_stats = all_stats.where(BattingStat.season << season)
# if position is not None:
# all_stats = all_stats.where(BattingStat.pos << position)
#
# if group_by == 'player':
# all_stats = all_stats.group_by(SQL('player'))
# elif group_by == 'playerteam':
# all_stats = all_stats.group_by(SQL('player'), BattingStat.team)
# elif group_by == 'playergame':
# all_stats = all_stats.group_by(SQL('player'), BattingStat.game_id)
# elif group_by == 'team':
# all_stats = all_stats.group_by(BattingStat.team)
# elif group_by == 'teamgame':
# all_stats = all_stats.group_by(BattingStat.team, BattingStat.game_id)
# elif group_by == 'league':
# all_stats = all_stats.group_by(BattingStat.season)
#
# if sort == 'pa-desc':
# all_stats = all_stats.order_by(SQL('sum_pa').desc())
# elif sort == 'newest':
# all_stats = all_stats.order_by(-BattingStat.game_id)
# elif sort == 'oldest':
# all_stats = all_stats.order_by(BattingStat.game_id)
#
# if limit is not None:
# if limit < 1:
# limit = 1
# all_stats = all_stats.limit(limit)
#
# logging.info(f'bat_plays query: {all_stats}')
#
# return_stats = {
# 'count': all_stats.count(),
# 'stats': [{
# 'player': x.card.player_id if short_output else model_to_dict(x.card.player, recurse=False),
# 'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
# 'pa': x.sum_pa,
# 'ab': x.sum_ab,
# 'run': x.sum_run,
# 'hit': x.sum_hit,
# 'rbi': x.sum_rbi,
# 'double': x.sum_double,
# 'triple': x.sum_triple,
# 'hr': x.sum_hr,
# 'bb': x.sum_bb,
# 'so': x.sum_so,
# 'hbp': x.sum_hbp,
# 'sac': x.sum_sac,
# 'ibb': x.sum_ibb,
# 'gidp': x.sum_gidp,
# 'sb': x.sum_sb,
# 'cs': x.sum_cs,
# 'bphr': x.sum_bphr,
# 'bpfo': x.sum_bpfo,
# 'bp1b': x.sum_bp1b,
# 'bplo': x.sum_bplo,
# 'avg': x.sum_hit / max(x.sum_ab, 1),
# 'obp': (x.sum_hit + x.sum_bb + x.sum_hbp + x.sum_ibb) / max(x.sum_pa, 1),
# 'slg': (x.sum_hr * 4 + x.sum_triple * 3 + x.sum_double * 2 +
# (x.sum_hit - x.sum_double - x.sum_triple - x.sum_hr)) / max(x.sum_ab, 1),
# 'ops': ((x.sum_hit + x.sum_bb + x.sum_hbp + x.sum_ibb) / max(x.sum_pa, 1)) +
# ((x.sum_hr * 4 + x.sum_triple * 3 + x.sum_double * 2 +
# (x.sum_hit - x.sum_double - x.sum_triple - x.sum_hr)) / max(x.sum_ab, 1)),
# 'woba': (.69 * x.sum_bb + .72 * x.sum_hbp + .89 * (x.sum_hit - x.sum_double - x.sum_triple - x.sum_hr) +
# 1.27 * x.sum_double + 1.62 * x.sum_triple + 2.1 * x.sum_hr) / max(x.sum_pa - x.sum_ibb, 1),
# 'game': x.game_id
# } for x in all_stats]
# }
#
# db.close()
# return return_stats
#
#
# @app.get('/api/v1/plays/pitching')
# async def get_pitching_totals(
# player_id: list = Query(default=None), team_id: list = Query(default=None), season: list = Query(default=None),
# group_by: Literal['team', 'player', 'playerteam', 'playergame', 'teamgame', 'league'] = 'player',
# min_pa: Optional[int] = 1,
# sort: Optional[str] = None, limit: Optional[int] = None, short_output: Optional[bool] = False):
# all_stats = PitchingStat.select(
# PitchingStat.card, PitchingStat.team, PitchingStat.game_id, PitchingStat.vs_team,
# PitchingStat.card.player.alias('player'), fn.SUM(PitchingStat.ip).alias('sum_ip'),
# fn.SUM(PitchingStat.hit).alias('sum_hit'), fn.SUM(PitchingStat.run).alias('sum_run'),
# fn.SUM(PitchingStat.erun).alias('sum_erun'), fn.SUM(PitchingStat.so).alias('sum_so'),
# fn.SUM(PitchingStat.bb).alias('sum_bb'), fn.SUM(PitchingStat.hbp).alias('sum_hbp'),
# fn.SUM(PitchingStat.wp).alias('sum_wp'), fn.SUM(PitchingStat.balk).alias('sum_balk'),
# fn.SUM(PitchingStat.hr).alias('sum_hr'), fn.SUM(PitchingStat.ir).alias('sum_ir'),
# fn.SUM(PitchingStat.irs).alias('sum_irs'), fn.SUM(PitchingStat.gs).alias('sum_gs'),
# fn.SUM(PitchingStat.win).alias('sum_win'), fn.SUM(PitchingStat.loss).alias('sum_loss'),
# fn.SUM(PitchingStat.hold).alias('sum_hold'), fn.SUM(PitchingStat.sv).alias('sum_sv'),
# fn.SUM(PitchingStat.bsv).alias('sum_bsv'), fn.COUNT(PitchingStat.game_id).alias('sum_games')
# ).having(
# fn.SUM(PitchingStat.ip) >= max(min_pa / 3, 1)
# ).join(Card)
#
# if player_id is not None:
# all_cards = Card.select().where(Card.player_id << player_id)
# all_stats = all_stats.where(PitchingStat.card << all_cards)
# if team_id is not None:
# all_teams = Team.select().where(Team.id << team_id)
# all_stats = all_stats.where(PitchingStat.team << all_teams)
# if season is not None:
# all_stats = all_stats.where(PitchingStat.season << season)
#
# if group_by == 'player':
# all_stats = all_stats.group_by(SQL('player'))
# elif group_by == 'playerteam':
# all_stats = all_stats.group_by(SQL('player'), PitchingStat.team)
# elif group_by == 'playergame':
# all_stats = all_stats.group_by(SQL('player'), PitchingStat.game_id)
# elif group_by == 'team':
# all_stats = all_stats.group_by(PitchingStat.team)
# elif group_by == 'teamgame':
# all_stats = all_stats.group_by(PitchingStat.team, PitchingStat.game_id)
# elif group_by == 'league':
# all_stats = all_stats.group_by(PitchingStat.season)
#
# if sort == 'pa-desc':
# all_stats = all_stats.order_by(SQL('sum_pa').desc())
# elif sort == 'newest':
# all_stats = all_stats.order_by(-PitchingStat.game_id)
# elif sort == 'oldest':
# all_stats = all_stats.order_by(PitchingStat.game_id)
#
# if limit is not None:
# if limit < 1:
# limit = 1
# all_stats = all_stats.limit(limit)
#
# logging.info(f'bat_plays query: {all_stats}')
#
# return_stats = {
# 'count': all_stats.count(),
# 'stats': [{
# 'player': x.card.player_id if short_output else model_to_dict(x.card.player, recurse=False),
# 'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
# 'tbf': None,
# 'outs': round(x.sum_ip * 3),
# 'games': x.sum_games,
# 'gs': x.sum_gs,
# 'win': x.sum_win,
# 'loss': x.sum_loss,
# 'hold': x.sum_hold,
# 'save': x.sum_sv,
# 'bsave': x.sum_bsv,
# 'ir': x.sum_ir,
# 'ir_sc': x.sum_irs,
# 'runs': x.sum_run,
# 'e_runs': x.sum_erun,
# 'hits': x.sum_hit,
# 'hr': x.sum_hr,
# 'bb': x.sum_bb,
# 'so': x.sum_so,
# 'hbp': x.sum_hbp,
# 'wp': x.sum_wp,
# 'balk': x.sum_balk,
# 'era': (x.sum_erun * 27) / round(x.sum_ip * 3),
# 'whip': (x.sum_bb + x.sum_hit) / x.sum_ip,
# 'avg': None,
# 'obp': None,
# 'woba': None,
# 'k/9': x.sum_so * 9 / x.sum_ip,
# 'bb/9': x.sum_bb * 9 / x.sum_ip,
# 'k/bb': x.sum_so / max(x.sum_bb, .1),
# 'game': None,
# 'lob_2outs': None,
# 'rbi%': None
# } for x in all_stats]
# }
# db.close()
# return return_stats

View File

@ -0,0 +1,289 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import Literal, Optional, List
import logging
import pandas as pd
import pydantic
from pydantic import validator, root_validator
from ..db_engine import db, BattingCardRatings, model_to_dict, chunked, BattingCard, Player, query_to_csv, Team
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/battingcardratings',
tags=['battingcardratings']
)
class BattingCardRatingsModel(pydantic.BaseModel):
battingcard_id: int
vs_hand: Literal['R', 'L', 'vR', 'vL']
homerun: float = 0.0
bp_homerun: float = 0.0
triple: float = 0.0
double_three: float = 0.0
double_two: float = 0.0
double_pull: float = 0.0
single_two: float = 0.0
single_one: float = 0.0
single_center: float = 0.0
bp_single: float = 0.0
hbp: float = 0.0
walk: float = 0.0
strikeout: float = 0.0
lineout: float = 0.0
popout: float = 0.0
flyout_a: float = 0.0
flyout_bq: float = 0.0
flyout_lf_b: float = 0.0
flyout_rf_b: float = 0.0
groundout_a: float = 0.0
groundout_b: float = 0.0
groundout_c: float = 0.0
avg: float = 0.0
obp: float = 0.0
slg: float = 0.0
pull_rate: float = 0.0
center_rate: float = 0.0
slap_rate: float = 0.0
@validator("avg", always=True)
def avg_validator(cls, v, values, **kwargs):
return (values['homerun'] + values['bp_homerun'] / 2 + values['triple'] + values['double_three'] +
values['double_two'] + values['double_pull'] + values['single_two'] + values['single_one'] +
values['single_center'] + values['bp_single'] / 2) / 108
@validator("obp", always=True)
def obp_validator(cls, v, values, **kwargs):
return ((values['hbp'] + values['walk']) / 108) + values['avg']
@validator("slg", always=True)
def slg_validator(cls, v, values, **kwargs):
return (values['homerun'] * 4 + values['bp_homerun'] * 2 + values['triple'] * 3 + values['double_three'] * 2 +
values['double_two'] * 2 + values['double_pull'] * 2 + values['single_two'] + values['single_one'] +
values['single_center'] + values['bp_single'] / 2) / 108
@root_validator
def validate_chance_total(cls, values):
total_chances = (
values['homerun'] + values['bp_homerun'] + values['triple'] + values['double_three'] +
values['double_two'] + values['double_pull'] + values['single_two'] + values['single_one'] +
values['single_center'] + values['bp_single'] + values['hbp'] + values['walk'] +
values['strikeout'] + values['lineout'] + values['popout'] + values['flyout_a'] +
values['flyout_bq'] + values['flyout_lf_b'] + values['flyout_rf_b'] + values['groundout_a'] +
values['groundout_b'] + values['groundout_c'])
if round(total_chances) != 108:
raise ValueError("Must have exactly 108 chances on the card")
return values
class RatingsList(pydantic.BaseModel):
ratings: List[BattingCardRatingsModel]
@router.get('')
async def get_card_ratings(
team_id: int, ts: str, battingcard_id: list = Query(default=None), cardset_id: list = Query(default=None),
vs_hand: Literal['R', 'L', 'vR', 'vL'] = None, short_output: bool = False, csv: bool = False):
this_team = Team.get_or_none(Team.id == team_id)
logging.debug(f'Team: {this_team} / has_guide: {this_team.has_guide}')
if this_team is None or ts != this_team.team_hash() or this_team.has_guide != 1:
logging.warning(f'Team_id {team_id} attempted to pull ratings')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to pull card ratings.'
)
# elif not valid_token(token):
# logging.warning(f'Bad Token: {token}')
# db.close()
# raise HTTPException(
# status_code=401,
# detail='You are not authorized to pull card ratings.'
# )
all_ratings = BattingCardRatings.select()
if battingcard_id is not None:
all_ratings = all_ratings.where(BattingCardRatings.battingcard_id << battingcard_id)
if vs_hand is not None:
all_ratings = all_ratings.where(BattingCardRatings.vs_hand == vs_hand[-1])
if cardset_id is not None:
set_players = Player.select(Player.player_id).where(Player.cardset_id << cardset_id)
set_cards = BattingCard.select(BattingCard.id).where(BattingCard.player << set_players)
all_ratings = all_ratings.where(BattingCardRatings.battingcard << set_cards)
if csv:
# return_val = query_to_csv(all_ratings)
return_vals = [model_to_dict(x) for x in all_ratings]
for x in return_vals:
x.update(x['battingcard'])
x['player_id'] = x['battingcard']['player']['player_id']
del x['battingcard'], x['player']
db.close()
return Response(content=pd.DataFrame(return_vals).to_csv(index=False), media_type='text/csv')
else:
return_val = {'count': all_ratings.count(), 'ratings': [
model_to_dict(x, recurse=not short_output) for x in all_ratings
]}
db.close()
return return_val
@router.get('/scouting')
async def get_card_scouting(team_id: int, ts: str, cardset_id: list = Query(default=None)):
this_team = Team.get_or_none(Team.id == team_id)
logging.debug(f'Team: {this_team} / has_guide: {this_team.has_guide}')
if this_team is None or ts != this_team.team_hash() or this_team.has_guide != 1:
logging.warning(f'Team_id {team_id} attempted to pull ratings')
db.close()
return 'Your team does not have the ratings guide enabled. If you have purchased a copy ping Cal to ' \
'make sure it is enabled on your team. If you are interested you can pick it up here (thank you!): ' \
'https://ko-fi.com/manticorum/shop'
all_ratings = BattingCardRatings.select()
if cardset_id is not None:
set_players = Player.select(Player.player_id).where(Player.cardset_id << cardset_id)
set_cards = BattingCard.select(BattingCard.id).where(BattingCard.player << set_players)
all_ratings = all_ratings.where(BattingCardRatings.battingcard << set_cards)
vl_query = all_ratings.where(BattingCardRatings.vs_hand == 'L')
vr_query = all_ratings.where(BattingCardRatings.vs_hand == 'R')
vl_vals = [model_to_dict(x) for x in vl_query]
for x in vl_vals:
x.update(x['battingcard'])
x['player_id'] = x['battingcard']['player']['player_id']
x['player_name'] = x['battingcard']['player']['p_name']
x['rarity'] = x['battingcard']['player']['rarity']['name']
x['cardset_id'] = x['battingcard']['player']['cardset']['id']
x['cardset_name'] = x['battingcard']['player']['cardset']['name']
del x['battingcard']
del x['player']
vr_vals = [model_to_dict(x) for x in vr_query]
for x in vr_vals:
x['player_id'] = x['battingcard']['player']['player_id']
del x['battingcard']
vl = pd.DataFrame(vl_vals)
vr = pd.DataFrame(vr_vals)
db.close()
output = pd.merge(vl, vr, on='player_id', suffixes=('_vl', '_vr'))
first = ['player_id', 'player_name', 'cardset_name', 'rarity', 'hand', 'variant']
exclude = first + ['id_vl', 'id_vr', 'vs_hand_vl', 'vs_hand_vr']
output = output[first + [col for col in output.columns if col not in exclude]].sort_values(by=['player_id'])
# output = output.sort_values(by=['player_id'])
return Response(content=pd.DataFrame(output).to_csv(index=False), media_type='text/csv')
@router.get('/{ratings_id}')
async def get_one_rating(ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to pull card ratings.'
)
this_rating = BattingCardRatings.get_or_none(BattingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(status_code=404, detail=f'BattingCardRating id {ratings_id} not found')
r_data = model_to_dict(this_rating)
db.close()
return r_data
@router.get('/player/{player_id}')
async def get_player_ratings(
player_id: int, variant: list = Query(default=None), short_output: bool = False,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to pull card ratings.'
)
all_cards = BattingCard.select().where(BattingCard.player_id == player_id).order_by(BattingCard.variant)
if variant is not None:
all_cards = all_cards.where(BattingCard.variant << variant)
all_ratings = BattingCardRatings.select().where(BattingCardRatings.battingcard << all_cards)
return_val = {'count': all_ratings.count(), 'ratings': [
model_to_dict(x, recurse=not short_output) for x in all_ratings
]}
db.close()
return return_val
@router.put('')
async def put_ratings(ratings: RatingsList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post card ratings.'
)
new_ratings = []
updates = 0
for x in ratings.ratings:
try:
BattingCardRatings.get(
(BattingCardRatings.battingcard_id == x.battingcard_id) & (BattingCardRatings.vs_hand == x.vs_hand)
)
updates += BattingCardRatings.update(x.dict()).where(
(BattingCardRatings.battingcard_id == x.battingcard_id) & (BattingCardRatings.vs_hand == x.vs_hand)
).execute()
except BattingCardRatings.DoesNotExist:
new_ratings.append(x.dict())
with db.atomic():
for batch in chunked(new_ratings, 30):
BattingCardRatings.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Updated ratings: {updates}; new ratings: {len(new_ratings)}'
@router.delete('/{ratings_id}')
async def delete_rating(
ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post card ratings.'
)
this_rating = BattingCardRatings.get_or_none(BattingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(status_code=404, detail=f'BattingCardRating id {ratings_id} not found')
count = this_rating.delete_instance()
db.close()
if count == 1:
return f'Rating {this_rating} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Rating {this_rating} could not be deleted')

View File

@ -0,0 +1,224 @@
import random
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Literal, Optional, List
import logging
import pydantic
from ..db_engine import db, BattingCard, model_to_dict, fn, chunked, Player, MlbPlayer
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/battingcards',
tags=['battingcards']
)
class BattingCardModel(pydantic.BaseModel):
player_id: int
variant: int = 0
steal_low: int = 3
steal_high: int = 20
steal_auto: bool = False
steal_jump: float = 0
bunting: str = 'C'
hit_and_run: str = 'C'
running: int = 10
offense_col: int = None
hand: Literal['R', 'L', 'S'] = 'R'
class BattingCardList(pydantic.BaseModel):
cards: List[BattingCardModel]
@router.get('')
async def get_batting_cards(
player_id: list = Query(default=None), player_name: list = Query(default=None),
cardset_id: list = Query(default=None), short_output: bool = False, limit: Optional[int] = None):
all_cards = BattingCard.select()
if player_id is not None:
all_cards = all_cards.where(BattingCard.player_id << player_id)
if cardset_id is not None:
all_players = Player.select().where(Player.cardset_id << cardset_id)
all_cards = all_cards.where(BattingCard.player << all_players)
if player_name is not None:
name_list = [x.lower() for x in player_name]
all_players = Player.select().where(fn.lower(Player.p_name) << name_list)
all_cards = all_cards.where(BattingCard.player << all_players)
if limit is not None:
all_cards = all_cards.limit(limit)
return_val = {'count': all_cards.count(), 'cards': [
model_to_dict(x, recurse=not short_output) for x in all_cards
]}
db.close()
return return_val
@router.get('/{card_id}')
async def get_one_card(card_id: int):
this_card = BattingCard.get_or_none(BattingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(status_code=404, detail=f'BattingCard id {card_id} not found')
r_card = model_to_dict(this_card)
db.close()
return r_card
@router.get('/player/{player_id}')
async def get_player_cards(player_id: int, variant: list = Query(default=None), short_output: bool = False):
all_cards = BattingCard.select().where(BattingCard.player_id == player_id).order_by(BattingCard.variant)
if variant is not None:
all_cards = all_cards.where(BattingCard.variant << variant)
return_val = {'count': all_cards.count(), 'cards': [
model_to_dict(x, recurse=not short_output) for x in all_cards
]}
db.close()
return return_val
@router.put('')
async def put_cards(cards: BattingCardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post batting cards. This event has been logged.'
)
new_cards = []
updates = 0
logging.info(f'here!')
for x in cards.cards:
try:
old = BattingCard.get(
(BattingCard.player_id == x.player_id) & (BattingCard.variant == x.variant)
)
if x.offense_col is None:
x.offense_col = old.offense_col
updates += BattingCard.update(x.dict()).where(
(BattingCard.player_id == x.player_id) & (BattingCard.variant == x.variant)
).execute()
except BattingCard.DoesNotExist:
if x.offense_col is None:
this_player = Player.get_or_none(Player.player_id == x.player_id)
mlb_player = MlbPlayer.get_or_none(MlbPlayer.key_bbref == this_player.bbref_id)
if mlb_player is not None:
logging.info(f'setting offense_col to {mlb_player.offense_col} for {this_player.p_name}')
x.offense_col = mlb_player.offense_col
else:
logging.info(f'randomly setting offense_col for {this_player.p_name}')
x.offense_col = random.randint(1, 3)
logging.debug(f'x.dict(): {x.dict()}')
new_cards.append(x.dict())
with db.atomic():
for batch in chunked(new_cards, 30):
BattingCard.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Updated cards: {updates}; new cards: {len(new_cards)}'
@router.patch('/{card_id}')
async def patch_card(
card_id: int, steal_low: Optional[int] = None, steal_high: Optional[int] = None,
steal_auto: Optional[bool] = None, steal_jump: Optional[float] = None, bunting: Optional[str] = None,
hit_and_run: Optional[str] = None, running: Optional[int] = None, offense_col: Optional[int] = None,
hand: Literal['R', 'L', 'S'] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch batting cards. This event has been logged.'
)
this_card = BattingCard.get_or_none(BattingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(status_code=404, detail=f'BattingCard id {card_id} not found')
if steal_low is not None:
this_card.steal_low = steal_low
if steal_high is not None:
this_card.steal_high = steal_high
if steal_auto is not None:
this_card.steal_auto = steal_auto
if steal_jump is not None:
this_card.steal_jump = steal_jump
if bunting is not None:
this_card.bunting = bunting
if hit_and_run is not None:
this_card.hit_and_run = hit_and_run
if running is not None:
this_card.running = running
if offense_col is not None:
this_card.offense_col = offense_col
if hand is not None:
this_card.hand = hand
if this_card.save() == 1:
return_val = model_to_dict(this_card)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that card'
)
@router.delete('/{card_id}')
async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete batting cards. This event has been logged.'
)
this_card = BattingCard.get_or_none(BattingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(status_code=404, detail=f'BattingCard id {card_id} not found')
count = this_card.delete_instance()
db.close()
if count == 1:
return f'Card {this_card} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Card {this_card} could not be deleted')
@router.delete('')
async def delete_all_cards(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete batting cards. This event has been logged.'
)
d_query = BattingCard.delete()
d_query.execute()
return f'Deleted {d_query.count()} batting cards'

View File

@ -0,0 +1,158 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Literal, Optional, List
import logging
import pydantic
from pydantic import root_validator
from ..db_engine import db, CardPosition, model_to_dict, chunked, Player, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/cardpositions',
tags=['cardpositions']
)
class CardPositionModel(pydantic.BaseModel):
player_id: int
variant: int = 0
position: Literal['P', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF', 'DH']
innings: int = 1
range: int = 5
error: int = 0
arm: Optional[int] = None
pb: Optional[int] = None
overthrow: Optional[int] = None
@root_validator
def position_validator(cls, values):
if values['position'] in ['C', 'LF', 'CF', 'RF'] and values['arm'] is None:
raise ValueError(f'{values["position"]} must have an arm rating')
if values['position'] == 'C' and (values['pb'] is None or values['overthrow'] is None):
raise ValueError('Catchers must have a pb and overthrow rating')
return values
class PositionList(pydantic.BaseModel):
positions: List[CardPositionModel]
@router.get('')
async def get_card_positions(
player_id: list = Query(default=None), position: list = Query(default=None), min_innings: Optional[int] = 1,
r: list = Query(default=None), e: list = Query(default=None), arm: list = Query(default=None),
pb: list = Query(default=None), overthrow: list = Query(default=None), cardset_id: list = Query(default=None),
short_output: Optional[bool] = False, sort: Optional[str] = 'innings-desc'):
all_pos = CardPosition.select().where(CardPosition.innings >= min_innings).order_by(
CardPosition.player, CardPosition.position, CardPosition.variant
)
if player_id is not None:
all_pos = all_pos.where(CardPosition.player_id << player_id)
if position is not None:
p_list = [x.lower() for x in position]
all_pos = all_pos.where(fn.Lower(CardPosition.position) << p_list)
if r is not None:
all_pos = all_pos.where(CardPosition.range << r)
if e is not None:
all_pos = all_pos.where(CardPosition.error << e)
if arm is not None:
all_pos = all_pos.where(CardPosition.arm << arm)
if pb is not None:
all_pos = all_pos.where(CardPosition.pb << pb)
if overthrow is not None:
all_pos = all_pos.where(CardPosition.overthrow << overthrow)
if cardset_id is not None:
all_players = Player.select().where(Player.cardset_id << cardset_id)
all_pos = all_pos.where(CardPosition.player << all_players)
if sort == 'innings-desc':
all_pos = all_pos.order_by(CardPosition.innings.desc())
elif sort == 'innings-asc':
all_pos = all_pos.order_by(CardPosition.innings)
elif sort == 'range-desc':
all_pos = all_pos.order_by(CardPosition.range.desc())
elif sort == 'range-asc':
all_pos = all_pos.order_by(CardPosition.range)
return_val = {'count': all_pos.count(), 'positions': [
model_to_dict(x, recurse=not short_output) for x in all_pos
]}
db.close()
return return_val
@router.get('/{position_id}')
async def get_one_position(position_id: int):
this_pos = CardPosition.get_or_none(CardPosition.id == position_id)
if this_pos is None:
db.close()
raise HTTPException(status_code=404, detail=f'CardPosition id {position_id} not found')
r_data = model_to_dict(this_pos)
db.close()
return r_data
@router.put('')
async def put_positions(positions: PositionList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post card positions. This event has been logged.'
)
new_cards = []
updates = 0
for x in positions.positions:
try:
CardPosition.get(
(CardPosition.player_id == x.player_id) & (CardPosition.variant == x.variant) &
(CardPosition.position == x.position)
)
updates += CardPosition.update(x.dict()).where(
(CardPosition.player_id == x.player_id) & (CardPosition.variant == x.variant) &
(CardPosition.position == x.position)
).execute()
except CardPosition.DoesNotExist:
new_cards.append(x.dict())
with db.atomic():
for batch in chunked(new_cards, 30):
CardPosition.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Updated cards: {updates}; new cards: {len(new_cards)}'
@router.delete('/{position_id}')
async def delete_position(position_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete card positions. This event has been logged.'
)
this_pos = CardPosition.get_or_none(CardPosition.id == position_id)
if this_pos is None:
db.close()
raise HTTPException(status_code=404, detail=f'CardPosition id {position_id} not found')
count = this_pos.delete_instance()
db.close()
if count == 1:
return f'Card Position {this_pos} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Card Position {this_pos} could not be deleted')

356
app/routers_v2/cards.py Normal file
View File

@ -0,0 +1,356 @@
from fastapi import APIRouter, Depends, HTTPException, Response, Query
from typing import Optional, List
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Card, model_to_dict, Team, Player, Pack, Paperdex, CARDSETS
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/cards',
tags=['cards']
)
class CardPydantic(pydantic.BaseModel):
player_id: int
team_id: int
pack_id: int
value: Optional[int] = 0
class CardModel(pydantic.BaseModel):
cards: List[CardPydantic]
@router.get('')
async def get_cards(
player_id: Optional[int] = None, team_id: Optional[int] = None, pack_id: Optional[int] = None,
value: Optional[int] = None, min_value: Optional[int] = None, max_value: Optional[int] = None,
order_by: Optional[str] = None, limit: Optional[int] = None, dupes: Optional[bool] = None,
csv: Optional[bool] = None):
all_cards = Card.select()
# if all_cards.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'There are no cards to filter')
if team_id is not None:
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
all_cards = all_cards.where(Card.team == this_team)
if player_id is not None:
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
all_cards = all_cards.where(Card.player == this_player)
if pack_id is not None:
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No pack found with id {pack_id}')
all_cards = all_cards.where(Card.pack == this_pack)
if value is not None:
all_cards = all_cards.where(Card.value == value)
if min_value is not None:
all_cards = all_cards.where(Card.value >= min_value)
if max_value is not None:
all_cards = all_cards.where(Card.value <= max_value)
if order_by is not None:
if order_by.lower() == 'new':
all_cards = all_cards.order_by(-Card.id)
if limit is not None:
all_cards = all_cards.limit(limit)
if dupes:
if team_id is None:
raise HTTPException(status_code=400, detail='Dupe checking must include a team_id')
logging.info(f'dupe check')
p_query = Card.select(Card.player).where(Card.team_id == team_id)
seen = set()
dupes = []
for x in p_query:
if x.player.player_id in seen:
dupes.append(x.player_id)
else:
seen.add(x.player_id)
all_cards = all_cards.where(Card.player_id << dupes)
# if all_cards.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No cards found')
if csv:
data_list = [['id', 'player', 'cardset', 'rarity', 'team', 'pack', 'value']]
for line in all_cards:
data_list.append(
[
line.id, line.player.p_name, line.player.cardset, line.player.rarity, line.team.abbrev, line.pack,
line.value
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_cards.count(), 'cards': []}
for x in all_cards:
this_record = model_to_dict(x)
logging.debug(f'this_record: {this_record}')
this_dex = Paperdex.select().where(Paperdex.player == x)
this_record['player']['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
for y in this_dex:
this_record['player']['paperdex']['paperdex'].append(model_to_dict(y, recurse=False))
return_val['cards'].append(this_record)
# return_val['cards'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{card_id}')
async def v1_cards_get_one(card_id, csv: Optional[bool] = False):
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No card found with id {card_id}')
if csv:
data_list = [
['id', 'player', 'team', 'pack', 'value', 'roster1', 'roster2', 'roster3'],
[this_card.id, this_card.player, this_card.team.abbrev, this_card.pack, this_card.value,
this_card.roster1.name, this_card.roster2.name, this_card.roster3.name]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_card)
db.close()
return return_val
@router.post('')
async def v1_cards_post(cards: CardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post cards. This event has been logged.'
)
last_card = Card.select(Card.id).order_by(-Card.id).limit(1)
lc_id = last_card[0].id
new_cards = []
player_ids = []
inc_dex = True
this_team = Team.get_by_id(cards.cards[0].team_id)
if this_team.is_ai or 'Gauntlet' in this_team.abbrev:
inc_dex = False
# new_dex = []
# now = int(datetime.timestamp(datetime.now()) * 1000)
for x in cards.cards:
this_card = Card(
player_id=x.player_id,
team_id=x.team_id,
pack_id=x.pack_id,
value=x.value
)
if inc_dex:
Paperdex.get_or_create(team_id=x.team_id, player_id=x.player_id)
player_ids.append(x.player_id)
new_cards.append(this_card)
with db.atomic():
Card.bulk_create(new_cards, batch_size=15)
cost_query = Player.update(cost=Player.cost + 1).where(Player.player_id << player_ids)
cost_query.execute()
# sheets.post_new_cards(SHEETS_AUTH, lc_id)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_cards)} cards have been added')
# @router.post('/ai-update')
# async def v1_cards_ai_update(token: str = Depends(oauth2_scheme)):
# if not valid_token(token):
# logging.warning(f'Bad Token: {token}')
# db.close()
# raise HTTPException(
# status_code=401,
# detail='You are not authorized to update AI cards. This event has been logged.'
# )
#
# sheets.send_ai_cards(SHEETS_AUTH)
# raise HTTPException(status_code=200, detail=f'Just sent AI cards to sheets')
@router.post('/legal-check/{rarity_name}')
async def v1_cards_legal_check(
rarity_name: str, card_id: list = Query(default=None), token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='Unauthorized'
)
if rarity_name not in CARDSETS.keys():
return f'Rarity name {rarity_name} not a valid check'
bad_cards = []
all_cards = Card.select().where(Card.id << card_id)
for x in all_cards:
if x.player.cardset_id not in CARDSETS[rarity_name]:
if x.player.p_name in x.player.description:
bad_cards.append(x.player.description)
else:
bad_cards.append(f'{x.player.description} {x.player.p_name}')
return {'count': len(bad_cards), 'bad_cards': bad_cards}
@router.post('/post-update/{starting_id}')
async def v1_cards_post_update(starting_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to update card lists. This event has been logged.'
)
# sheets.post_new_cards(SHEETS_AUTH, starting_id)
db.close()
raise HTTPException(status_code=200, detail=f'Just sent cards to sheets starting at ID {starting_id}')
@router.post('/post-delete')
async def v1_cards_post_delete(del_ids: str, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete card lists. This event has been logged.'
)
logging.info(f'del_ids: {del_ids} / type: {type(del_ids)}')
# sheets.post_deletion(SHEETS_AUTH, del_ids.split(','))
@router.post('/wipe-team/{team_id}')
async def v1_cards_wipe_team(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to wipe teams. This event has been logged.'
)
try:
this_team = Team.get_by_id(team_id)
except Exception as e:
logging.error(f'/cards/wipe-team/{team_id} - could not find team')
raise HTTPException(status_code=404, detail=f'Team {team_id} not found')
t_query = Card.update(team=None).where(Card.team == this_team).execute()
db.close()
return f'Wiped {t_query} cards'
@router.patch('/{card_id}')
async def v1_cards_patch(
card_id, player_id: Optional[int] = None, team_id: Optional[int] = None, pack_id: Optional[int] = None,
value: Optional[int] = None, roster1_id: Optional[int] = None, roster2_id: Optional[int] = None,
roster3_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch cards. This event has been logged.'
)
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No card found with id {card_id}')
if player_id is not None:
this_card.player_id = player_id
if team_id is not None:
if team_id == 0:
this_card.team_id = None
else:
this_card.team_id = team_id
if pack_id is not None:
this_card.pack_id = pack_id
if value is not None:
this_card.value = value
if roster1_id is not None:
this_card.roster1_id = roster1_id
if roster2_id is not None:
this_card.roster2_id = roster2_id
if roster3_id is not None:
this_card.roster3_id = roster3_id
if this_card.save() == 1:
return_val = model_to_dict(this_card)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{card_id}')
async def v1_cards_delete(card_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete packs. This event has been logged.'
)
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No cards found with id {card_id}')
count = this_card.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Card {card_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Card {card_id} was not deleted')

204
app/routers_v2/cardsets.py Normal file
View File

@ -0,0 +1,204 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Cardset, model_to_dict, fn, Event
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/cardsets',
tags=['cardsets']
)
class CardsetModel(pydantic.BaseModel):
name: str
description: str
event_id: Optional[int] = None
in_packs: Optional[bool] = True
total_cards: int = 0
for_purchase: Optional[bool] = True
ranked_legal: Optional[bool] = True
@router.get('')
async def get_cardsets(
name: Optional[str] = None, in_desc: Optional[str] = None, event_id: Optional[int] = None,
in_packs: Optional[bool] = None, ranked_legal: Optional[bool] = None, csv: Optional[bool] = None):
all_cardsets = Cardset.select()
if all_cardsets.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no cardsets to filter')
if name is not None:
all_cardsets = all_cardsets.where(fn.Lower(Cardset.name) == name.lower())
if in_desc is not None:
all_cardsets = all_cardsets.where(fn.Lower(Cardset.description).contains(in_desc.lower()))
if event_id is not None:
try:
this_event = Event.get_by_id(event_id)
all_cardsets = all_cardsets.where(Cardset.event == this_event)
except Exception as e:
logging.error(f'Failed to find event {event_id}: {e}')
raise HTTPException(status_code=404, detail=f'Event id {event_id} not found')
if in_packs is not None:
all_cardsets = all_cardsets.where(Cardset.in_packs == in_packs)
if ranked_legal is not None:
all_cardsets = all_cardsets.where(Cardset.ranked_legal == ranked_legal)
if all_cardsets.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No cardsets found')
if csv:
data_list = [[
'id', 'name', 'description', 'event_id', 'in_packs', 'for_purchase', 'total_cards', 'ranked_legal'
]]
for line in all_cardsets:
data_list.append(
[
line.id, line.name, line.description, line.event.id if line.event else '', line.in_packs,
line.for_purchase, line.total_cards, line.ranked_legal
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_cardsets.count(), 'cardsets': []}
for x in all_cardsets:
return_val['cardsets'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{cardset_id}')
async def get_one_cardset(cardset_id, csv: Optional[bool] = False):
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
if csv:
data_list = [
['id', 'name', 'description'],
[this_cardset.id, this_cardset.name, this_cardset.description]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_cardset)
db.close()
return return_val
@router.post('')
async def post_cardsets(cardset: CardsetModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post cardsets. This event has been logged.'
)
dupe_set = Cardset.get_or_none(Cardset.name == cardset.name)
if dupe_set:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a cardset using {cardset.name}')
this_cardset = Cardset(**cardset.__dict__)
saved = this_cardset.save()
if saved == 1:
return_val = model_to_dict(this_cardset)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that cardset'
)
@router.patch('/{cardset_id}')
async def patch_cardsets(
cardset_id, name: Optional[str] = None, description: Optional[str] = None, in_packs: Optional[bool] = None,
for_purchase: Optional[bool] = None, total_cards: Optional[int] = None, ranked_legal: Optional[bool] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch cardsets. This event has been logged.'
)
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
if name is not None:
this_cardset.name = name
if description is not None:
this_cardset.description = description
if in_packs is not None:
this_cardset.in_packs = in_packs
if for_purchase is not None:
this_cardset.for_purchase = for_purchase
if total_cards is not None:
this_cardset.total_cards = total_cards
if ranked_legal is not None:
this_cardset.ranked_legal = ranked_legal
if this_cardset.save() == 1:
return_val = model_to_dict(this_cardset)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{cardset_id}')
async def delete_cardsets(cardset_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete cardsets. This event has been logged.'
)
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
count = this_cardset.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Cardset {cardset_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Cardset {cardset_id} was not deleted')

166
app/routers_v2/current.py Normal file
View File

@ -0,0 +1,166 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from pandas import DataFrame
from typing import Optional
import logging
import pydantic
from ..db_engine import db, Current, model_to_dict
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/current',
tags=['current']
)
class CurrentModel(pydantic.BaseModel):
season: int
week: int
gsheet_template: str
gsheet_version: str
@router.get('')
async def get_current(season: Optional[int] = None, csv: Optional[bool] = False):
if season:
current = Current.get_or_none(season=season)
else:
current = Current.latest()
if csv:
current_list = [
['id', 'season', 'week'],
[current.id, current.season, current.week]
]
return_val = DataFrame(current_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(current)
db.close()
return return_val
@router.get('/{current_id}')
async def get_one_current(current_id, csv: Optional[bool] = False):
try:
current = Current.get_by_id(current_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No current found with id {current_id}')
if csv:
current_list = [
['id', 'season', 'week'],
[current.id, current.season, current.week]
]
return_val = DataFrame(current_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(current)
db.close()
return return_val
@router.post('')
async def post_current(current: CurrentModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post current. This event has been logged.'
)
dupe_curr = Current.get_or_none(Current.season == current.season)
if dupe_curr:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a current for season {current.season}')
this_curr = Current(
season=current.season,
week=current.week,
gsheet_template=current.gsheet_template,
gsheet_version=current.gsheet_version
)
saved = this_curr.save()
if saved == 1:
return_val = model_to_dict(this_curr)
db.close()
return return_val
else:
raise HTTPException(status_code=418, detail='Well slap my ass and call me a teapot; I could not save that team')
@router.patch('/{current_id}')
async def patch_current(
current_id: int, season: Optional[int] = None, week: Optional[int] = None,
gsheet_template: Optional[str] = None, gsheet_version: Optional[str] = None,
live_scoreboard: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch current. This event has been logged.'
)
try:
current = Current.get_by_id(current_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No current found with id {current_id}')
if season is not None:
current.season = season
if week is not None:
current.week = week
if gsheet_template is not None:
current.gsheet_template = gsheet_template
if gsheet_version is not None:
current.gsheet_version = gsheet_version
if live_scoreboard is not None:
current.live_scoreboard = live_scoreboard
if current.save() == 1:
return_val = model_to_dict(current)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that current'
)
@router.delete('/{current_id}')
async def delete_current(current_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete current. This event has been logged.'
)
try:
this_curr = Current.get_by_id(current_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No current found with id {current_id}')
count = this_curr.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Current {current_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Current {current_id} was not deleted')

230
app/routers_v2/decisions.py Normal file
View File

@ -0,0 +1,230 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import List, Optional, Literal
import copy
import logging
import pandas as pd
import pydantic
from ..db_engine import db, Decision, StratGame, Player, model_to_dict, chunked, fn, Team
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/decisions',
tags=['decisions']
)
class DecisionModel(pydantic.BaseModel):
game_id: int
season: int
week: int
pitcher_id: int
pitcher_team_id: int
win: int = 0
loss: int = 0
hold: int = 0
is_save: int = 0
is_start: bool = False
b_save: int = 0
irunners: int = 0
irunners_scored: int = 0
rest_ip: float = 0
rest_required: int = 0
class DecisionList(pydantic.BaseModel):
decisions: List[DecisionModel]
@router.get('')
async def get_decisions(
season: list = Query(default=None), week: list = Query(default=None), team_id: list = Query(default=None),
win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None, save: Optional[int] = None,
b_save: Optional[int] = None, irunners: list = Query(default=None), irunners_scored: list = Query(default=None),
game_type: list = Query(default=None),
game_id: list = Query(default=None), player_id: list = Query(default=None), csv: Optional[bool] = False,
limit: Optional[int] = 100, page_num: Optional[int] = 1, short_output: Optional[bool] = False):
all_dec = Decision.select().order_by(-Decision.season, -Decision.week, -Decision.id)
if season is not None:
all_dec = all_dec.where(Decision.season << season)
if week is not None:
all_dec = all_dec.where(Decision.week << week)
if game_id is not None:
all_dec = all_dec.where(Decision.game_id << game_id)
if player_id is not None:
all_dec = all_dec.where(Decision.pitcher_id << player_id)
if team_id is not None:
all_dec = all_dec.where(Decision.pitcher_team_id << team_id)
if win is not None:
all_dec = all_dec.where(Decision.win == win)
if loss is not None:
all_dec = all_dec.where(Decision.loss == loss)
if hold is not None:
all_dec = all_dec.where(Decision.hold == hold)
if save is not None:
all_dec = all_dec.where(Decision.save == save)
if b_save is not None:
all_dec = all_dec.where(Decision.b_save == b_save)
if irunners is not None:
all_dec = all_dec.where(Decision.irunners << irunners)
if irunners_scored is not None:
all_dec = all_dec.where(Decision.irunners_scored << irunners_scored)
if game_type is not None:
all_types = [x.lower() for x in game_type]
all_games = StratGame.select().where(fn.Lower(StratGame.game_type) << all_types)
all_dec = all_dec.where(Decision.game << all_games)
if limit < 1:
limit = 1
if limit > 100:
limit = 100
all_dec = all_dec.paginate(page_num, limit)
return_dec = {
'count': all_dec.count(),
'decisions': [model_to_dict(x, recurse=not short_output) for x in all_dec]
}
db.close()
if csv:
return_vals = return_dec['decisions']
if len(return_vals) == 0:
return Response(content=pd.DataFrame().to_csv(index=False), media_type='text/csv')
for x in return_vals:
x['game_id'] = x['game']['id']
x['game_type'] = x['game']['game_type']
x['player_id'] = x['pitcher']['player_id']
x['player_name'] = x['pitcher']['p_name']
x['player_cardset'] = x['pitcher']['cardset']['name']
x['team_id'] = x['pitcher_team']['id']
x['team_abbrev'] = x['pitcher_team']['abbrev']
del x['pitcher'], x['pitcher_team'], x['game']
output = pd.DataFrame(return_vals)
first = ['player_id', 'player_name', 'player_cardset', 'team_id', 'team_abbrev']
exclude = first + ['lob_all', 'lob_all_rate', 'lob_2outs', 'rbi%']
output = output[first + [col for col in output.columns if col not in exclude]]
db.close()
return Response(content=pd.DataFrame(output).to_csv(index=False), media_type='text/csv')
return return_dec
@router.patch('/{decision_id}')
async def patch_decision(
decision_id: int, win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None,
save: Optional[int] = None, b_save: Optional[int] = None, irunners: Optional[int] = None,
irunners_scored: Optional[int] = None, rest_ip: Optional[int] = None, rest_required: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_decision - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None:
db.close()
raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found')
if win is not None:
this_dec.win = win
if loss is not None:
this_dec.loss = loss
if hold is not None:
this_dec.hold = hold
if save is not None:
this_dec.is_save = save
if b_save is not None:
this_dec.b_save = b_save
if irunners is not None:
this_dec.irunners = irunners
if irunners_scored is not None:
this_dec.irunners_scored = irunners_scored
if rest_ip is not None:
this_dec.rest_ip = rest_ip
if rest_required is not None:
this_dec.rest_required = rest_required
if this_dec.save() == 1:
d_result = model_to_dict(this_dec)
db.close()
return d_result
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch decision {decision_id}')
@router.post('')
async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_decisions - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_dec = []
for x in dec_list.decisions:
if StratGame.get_or_none(StratGame.id == x.game_id) is None:
raise HTTPException(status_code=404, detail=f'Game ID {x.game_id} not found')
if Player.get_or_none(Player.player_id == x.pitcher_id) is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.pitcher_id} not found')
if Team.get_or_none(Team.id == x.pitcher_team_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.pitcher_team_id} not found')
new_dec.append(x.dict())
with db.atomic():
for batch in chunked(new_dec, 10):
Decision.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_dec)} decisions'
@router.delete('/{decision_id}')
async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_decision - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_dec = Decision.get_or_none(Decision.id == decision_id)
if this_dec is None:
db.close()
raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found')
count = this_dec.delete_instance()
db.close()
if count == 1:
return f'Decision {decision_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Decision {decision_id} could not be deleted')
@router.delete('/game/{game_id}')
async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_decisions_game - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f'Game ID {game_id} not found')
count = Decision.delete().where(Decision.game == this_game).execute()
db.close()
if count > 0:
return f'Deleted {count} decisions matching Game ID {game_id}'
else:
raise HTTPException(status_code=500, detail=f'No decisions matching Game ID {game_id} were deleted')

195
app/routers_v2/events.py Normal file
View File

@ -0,0 +1,195 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Event, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/events',
tags=['events']
)
class EventModel(pydantic.BaseModel):
name: str
short_desc: str
long_desc: str
url: Optional[str] = None
thumbnail: Optional[str] = None
active: Optional[bool] = False
@router.get('')
async def v1_events_get(
name: Optional[str] = None, in_desc: Optional[str] = None, active: Optional[bool] = None,
csv: Optional[bool] = None):
all_events = Event.select()
if name is not None:
all_events = all_events.where(fn.Lower(Event.name) == name.lower())
if in_desc is not None:
all_events = all_events.where(
(fn.Lower(Event.short_desc).contains(in_desc.lower())) |
(fn.Lower(Event.long_desc).contains(in_desc.lower()))
)
if active is not None:
all_events = all_events.where(Event.active == active)
if csv:
data_list = [['id', 'name', 'short_desc', 'long_desc', 'url', 'thumbnail', 'active']]
for line in all_events:
data_list.append(
[
line.id, line.name, line.short_desc, line.long_desc, line.url, line.thumbnail, line.active
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_events.count(), 'events': []}
for x in all_events:
return_val['events'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{event_id}')
async def v1_events_get_one(event_id, csv: Optional[bool] = False):
try:
this_event = Event.get_by_id(event_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No event found with id {event_id}')
if csv:
data_list = [
['id', 'name', 'short_desc', 'long_desc', 'url', 'thumbnail', 'active'],
[this_event.id, this_event.name, this_event.short_desc, this_event.long_desc, this_event.url,
this_event.thumbnail, this_event.active]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_event)
db.close()
return return_val
@router.post('')
async def v1_events_post(event: EventModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post events. This event has been logged.'
)
dupe_event = Event.get_or_none(Event.name == event.name)
if dupe_event:
db.close()
raise HTTPException(status_code=400, detail=f'There is already an event using {event.name}')
this_event = Event(
name=event.name,
short_desc=event.short_desc,
long_desc=event.long_desc,
url=event.url,
thumbnail=event.thumbnail,
active=event.active
)
saved = this_event.save()
if saved == 1:
return_val = model_to_dict(this_event)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that cardset'
)
@router.patch('/{event_id}')
async def v1_events_patch(
event_id, name: Optional[str] = None, short_desc: Optional[str] = None, long_desc: Optional[str] = None,
url: Optional[str] = None, thumbnail: Optional[str] = None, active: Optional[bool] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch events. This event has been logged.'
)
try:
this_event = Event.get_by_id(event_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No event found with id {event_id}')
if name is not None:
this_event.name = name
if short_desc is not None:
this_event.short_desc = short_desc
if long_desc is not None:
this_event.long_desc = long_desc
if url is not None:
this_event.url = url
if thumbnail is not None:
this_event.thumbnail = thumbnail
if active is not None:
this_event.active = active
if this_event.save() == 1:
return_val = model_to_dict(this_event)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that event'
)
@router.delete('/{event_id}')
async def v1_events_delete(event_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete events. This event has been logged.'
)
try:
this_event = Event.get_by_id(event_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No event found with id {event_id}')
count = this_event.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Event {event_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Event {event_id} was not deleted')

View File

@ -0,0 +1,192 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, GameRewards, model_to_dict
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/gamerewards',
tags=['gamerewards']
)
class GameRewardModel(pydantic.BaseModel):
name: str
pack_type_id: Optional[int] = None
player_id: Optional[int] = None
money: Optional[int] = None
@router.get('')
async def v1_gamerewards_get(
name: Optional[str] = None, pack_type_id: Optional[int] = None, player_id: Optional[int] = None,
money: Optional[int] = None, csv: Optional[bool] = None):
all_rewards = GameRewards.select()
# if all_rewards.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'There are no awards to filter')
if name is not None:
all_rewards = all_rewards.where(GameRewards.name == name)
if pack_type_id is not None:
all_rewards = all_rewards.where(GameRewards.pack_type_id == pack_type_id)
if player_id is not None:
all_rewards = all_rewards.where(GameRewards.player_id == player_id)
if money is not None:
all_rewards = all_rewards.where(GameRewards.money == money)
if csv:
data_list = [['id', 'pack_type_id', 'player_id', 'money']]
for line in all_rewards:
data_list.append([
line.id, line.pack_type_id if line.pack_type else None, line.player_id if line.player else None,
line.money
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_rewards.count(), 'gamerewards': []}
for x in all_rewards:
return_val['gamerewards'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{gameaward_id}')
async def v1_gamerewards_get_one(gamereward_id, csv: Optional[bool] = None):
try:
this_game_reward = GameRewards.get_by_id(gamereward_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No game reward found with id {gamereward_id}')
if csv:
data_list = [
['id', 'pack_type_id', 'player_id', 'money'],
[this_game_reward.id, this_game_reward.pack_type_id if this_game_reward.pack_type else None,
this_game_reward.player_id if this_game_reward.player else None, this_game_reward.money]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_game_reward)
db.close()
return return_val
@router.post('')
async def v1_gamerewards_post(game_reward: GameRewardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post game rewards. This event has been logged.'
)
this_award = GameRewards(
name=game_reward.name,
pack_type_id=game_reward.pack_type_id,
player_id=game_reward.player_id,
money=game_reward.money
)
saved = this_award.save()
if saved == 1:
return_val = model_to_dict(this_award)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that roster'
)
@router.patch('/{game_reward_id}')
async def v1_gamerewards_patch(
game_reward_id: int, name: Optional[str] = None, pack_type_id: Optional[int] = None,
player_id: Optional[int] = None, money: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch gamerewards. This event has been logged.'
)
try:
this_game_reward = GameRewards.get_by_id(game_reward_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No game reward found with id {game_reward_id}')
if name is not None:
this_game_reward.name = name
if pack_type_id is not None:
if not pack_type_id:
this_game_reward.pack_type_id = None
else:
this_game_reward.pack_type_id = pack_type_id
if player_id is not None:
if not player_id:
this_game_reward.player_id = None
else:
this_game_reward.player_id = player_id
if money is not None:
if not money:
this_game_reward.money = None
else:
this_game_reward.money = money
if this_game_reward.save() == 1:
return_val = model_to_dict(this_game_reward)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{gamereward_id}')
async def v1_gamerewards_delete(gamereward_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete awards. This event has been logged.'
)
try:
this_award = GameRewards.get_by_id(gamereward_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No award found with id {gamereward_id}')
count = this_award.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Game Reward {gamereward_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Game Reward {gamereward_id} was not deleted')

View File

@ -0,0 +1,139 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Optional, List
import logging
import pydantic
from ..db_engine import db, GauntletReward, model_to_dict, chunked, DatabaseError
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/gauntletrewards',
tags=['gauntletrewards']
)
class GauntletRewardModel(pydantic.BaseModel):
name: str
gauntlet_id: Optional[int] = 0
reward_id: Optional[int] = 0
win_num: Optional[int] = 0
loss_max: Optional[int] = 1
class GauntletRewardList(pydantic.BaseModel):
rewards: List[GauntletRewardModel]
@router.get('')
async def v1_gauntletreward_get(
name: Optional[str] = None, gauntlet_id: Optional[int] = None, reward_id: list = Query(default=None),
win_num: Optional[int] = None, loss_max: Optional[int] = None):
all_rewards = GauntletReward.select()
if name is not None:
all_rewards = all_rewards.where(GauntletReward.name == name)
if gauntlet_id is not None:
all_rewards = all_rewards.where(GauntletReward.gauntlet_id == gauntlet_id)
if reward_id is not None:
all_rewards = all_rewards.where(GauntletReward.reward_id << reward_id)
if win_num is not None:
all_rewards = all_rewards.where(GauntletReward.win_num == win_num)
if loss_max is not None:
all_rewards = all_rewards.where(GauntletReward.loss_max >= loss_max)
all_rewards = all_rewards.order_by(-GauntletReward.loss_max, GauntletReward.win_num)
return_val = {'count': all_rewards.count(), 'rewards': []}
for x in all_rewards:
return_val['rewards'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{gauntletreward_id}')
async def v1_gauntletreward_get_one(gauntletreward_id):
try:
this_reward = GauntletReward.get_by_id(gauntletreward_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No gauntlet reward found with id {gauntletreward_id}')
return_val = model_to_dict(this_reward)
db.close()
return return_val
@router.patch('/{gauntletreward_id}')
async def v1_gauntletreward_patch(
gauntletreward_id, name: Optional[str] = None, gauntlet_id: Optional[int] = None,
reward_id: Optional[int] = None, win_num: Optional[int] = None, loss_max: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch gauntlet rewards. This event has been logged.'
)
this_reward = GauntletReward.get_or_none(GauntletReward.id == gauntletreward_id)
if this_reward is None:
db.close()
raise KeyError(f'Gauntlet Reward ID {gauntletreward_id} not found')
if gauntlet_id is not None:
this_reward.gauntlet_id = gauntlet_id
if reward_id is not None:
this_reward.reward_id = reward_id
if win_num is not None:
this_reward.win_num = win_num
if loss_max is not None:
this_reward.loss_max = loss_max
if name is not None:
this_reward.name = name
if this_reward.save():
r_curr = model_to_dict(this_reward)
db.close()
return r_curr
else:
db.close()
raise DatabaseError(f'Unable to patch gauntlet reward {gauntletreward_id}')
@router.post('')
async def v1_gauntletreward_post(gauntletreward: GauntletRewardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post gauntlets. This event has been logged.'
)
all_rewards = []
for x in gauntletreward.rewards:
all_rewards.append(x.dict())
with db.atomic():
for batch in chunked(all_rewards, 15):
GauntletReward.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(all_rewards)} gauntlet rewards'
@router.delete('/{gauntletreward_id}')
async def v1_gauntletreward_delete(gauntletreward_id):
if GauntletReward.delete_by_id(gauntletreward_id) == 1:
return f'Deleted gauntlet reward ID {gauntletreward_id}'
raise DatabaseError(f'Unable to delete gauntlet run {gauntletreward_id}')

View File

@ -0,0 +1,169 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Optional
import logging
import pydantic
from ..db_engine import db, GauntletRun, model_to_dict, DatabaseError
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/gauntletruns',
tags=['notifs']
)
class GauntletRunModel(pydantic.BaseModel):
team_id: int
gauntlet_id: int
wins: Optional[int] = 0
losses: Optional[int] = 0
gsheet: Optional[str] = None
created: Optional[int] = int(datetime.timestamp(datetime.now())*1000)
ended: Optional[int] = 0
@router.get('')
async def get_gauntletruns(
team_id: list = Query(default=None), wins: Optional[int] = None, wins_min: Optional[int] = None,
wins_max: Optional[int] = None, losses: Optional[int] = None, losses_min: Optional[int] = None,
losses_max: Optional[int] = None, gsheet: Optional[str] = None, created_after: Optional[int] = None,
created_before: Optional[int] = None, ended_after: Optional[int] = None, ended_before: Optional[int] = None,
is_active: Optional[bool] = None, gauntlet_id: list = Query(default=None), season: list = Query(default=None)):
all_gauntlets = GauntletRun.select()
if team_id is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.team_id << team_id)
if wins is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.wins == wins)
if wins_min is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.wins >= wins_min)
if wins_max is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.wins <= wins_max)
if losses is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.losses == losses)
if losses_min is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.losses >= losses_min)
if losses_max is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.losses <= losses_max)
if gsheet is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.gsheet == gsheet)
if created_after is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.created >= created_after)
if created_before is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.created <= created_before)
if ended_after is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.ended >= ended_after)
if ended_before is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.ended <= ended_before)
if is_active is not None:
if is_active is True:
all_gauntlets = all_gauntlets.where(GauntletRun.ended == 0)
else:
all_gauntlets = all_gauntlets.where(GauntletRun.ended != 0)
if gauntlet_id is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.gauntlet_id << gauntlet_id)
if season is not None:
all_gauntlets = all_gauntlets.where(GauntletRun.team.season << season)
return_val = {'count': all_gauntlets.count(), 'runs': []}
for x in all_gauntlets:
return_val['runs'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{gauntletrun_id}')
async def get_one_gauntletrun(gauntletrun_id):
try:
this_gauntlet = GauntletRun.get_by_id(gauntletrun_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No gauntlet found with id {gauntletrun_id}')
return_val = model_to_dict(this_gauntlet)
db.close()
return return_val
@router.patch('/{gauntletrun_id}')
async def patch_gauntletrun(
gauntletrun_id, team_id: Optional[int] = None, wins: Optional[int] = None, losses: Optional[int] = None,
gsheet: Optional[str] = None, created: Optional[bool] = None, ended: Optional[bool] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch gauntlet runs. This event has been logged.'
)
this_run = GauntletRun.get_or_none(GauntletRun.id == gauntletrun_id)
if this_run is None:
db.close()
raise KeyError(f'Gauntlet Run ID {gauntletrun_id} not found')
if team_id is not None:
this_run.team_id = team_id
if wins is not None:
this_run.wins = wins
if losses is not None:
this_run.losses = losses
if gsheet is not None:
this_run.gsheet = gsheet
if created is not None:
if created is True:
this_run.created = int(datetime.timestamp(datetime.now())*1000)
else:
this_run.created = None
if ended is not None:
if ended is True:
this_run.ended = int(datetime.timestamp(datetime.now())*1000)
else:
this_run.ended = 0
if this_run.save():
r_curr = model_to_dict(this_run)
db.close()
return r_curr
else:
db.close()
raise DatabaseError(f'Unable to patch gauntlet run {gauntletrun_id}')
@router.post('')
async def post_gauntletrun(gauntletrun: GauntletRunModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post gauntlets. This event has been logged.'
)
this_run = GauntletRun(**gauntletrun.dict())
if this_run.save():
r_run = model_to_dict(this_run)
db.close()
return r_run
else:
db.close()
raise DatabaseError(f'Unable to post gauntlet run')
@router.delete('/{gauntletrun_id}')
async def delete_gauntletrun(gauntletrun_id):
if GauntletRun.delete_by_id(gauntletrun_id) == 1:
return f'Deleted gauntlet run ID {gauntletrun_id}'
raise DatabaseError(f'Unable to delete gauntlet run {gauntletrun_id}')

View File

@ -0,0 +1,189 @@
from fastapi import APIRouter, Depends, HTTPException, Response, Query
from typing import Optional, List
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, MlbPlayer, model_to_dict, fn, chunked, query_to_csv
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/mlbplayers',
tags=['mlbplayers']
)
class PlayerModel(pydantic.BaseModel):
first_name: str
last_name: str
key_fangraphs: int = None
key_bbref: str = None
key_retro: str = None
key_mlbam: int = None
offense_col: int = None
class PlayerList(pydantic.BaseModel):
players: List[PlayerModel]
@router.get('')
async def get_players(
full_name: list = Query(default=None), first_name: list = Query(default=None),
last_name: list = Query(default=None), key_fangraphs: list = Query(default=None),
key_bbref: list = Query(default=None), key_retro: list = Query(default=None),
key_mlbam: list = Query(default=None), offense_col: list = Query(default=None), csv: Optional[bool] = False):
all_players = MlbPlayer.select()
if full_name is not None:
name_list = [x.lower() for x in full_name]
all_players = all_players.where(
fn.lower(MlbPlayer.first_name) + ' ' + fn.lower(MlbPlayer.last_name) << name_list
)
if first_name is not None:
all_players = all_players.where(MlbPlayer.first_name << first_name)
if first_name is not None:
all_players = all_players.where(MlbPlayer.first_name << first_name)
if last_name is not None:
all_players = all_players.where(MlbPlayer.last_name << last_name)
if key_fangraphs is not None:
all_players = all_players.where(MlbPlayer.key_fangraphs << key_fangraphs)
if key_bbref is not None:
all_players = all_players.where(MlbPlayer.key_bbref << key_bbref)
if key_retro is not None:
all_players = all_players.where(MlbPlayer.key_retro << key_retro)
if key_mlbam is not None:
all_players = all_players.where(MlbPlayer.key_mlbam << key_mlbam)
if offense_col is not None:
all_players = all_players.where(MlbPlayer.offense_col << offense_col)
if csv:
return_val = query_to_csv(all_players)
db.close()
return Response(content=return_val, media_type='text/csv')
return_val = {'count': all_players.count(), 'players': [
model_to_dict(x) for x in all_players
]}
db.close()
return return_val
@router.get('/{player_id}')
async def get_one_player(player_id: int):
this_player = MlbPlayer.get_or_none(MlbPlayer.id == player_id)
if this_player is None:
db.close()
raise HTTPException(status_code=404, detail=f'MlbPlayer id {player_id} not found')
r_data = model_to_dict(this_player)
db.close()
return r_data
@router.patch('/{player_id}')
async def patch_player(
player_id: int, first_name: Optional[str] = None, last_name: Optional[str] = None,
key_fangraphs: Optional[str] = None, key_bbref: Optional[str] = None, key_retro: Optional[str] = None,
key_mlbam: Optional[str] = None, offense_col: Optional[str] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch mlb players. This event has been logged.'
)
this_player = MlbPlayer.get_or_none(MlbPlayer.id == player_id)
if this_player is None:
db.close()
raise HTTPException(status_code=404, detail=f'MlbPlayer id {player_id} not found')
if first_name is not None:
this_player.first_name = first_name
if last_name is not None:
this_player.last_name = last_name
if key_fangraphs is not None:
this_player.key_fangraphs = key_fangraphs
if key_bbref is not None:
this_player.key_bbref = key_bbref
if key_retro is not None:
this_player.key_retro = key_retro
if key_mlbam is not None:
this_player.key_mlbam = key_mlbam
if offense_col is not None:
this_player.offense_col = offense_col
if this_player.save() == 1:
return_val = model_to_dict(this_player)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that player'
)
@router.post('')
async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post mlb players. This event has been logged.'
)
new_players = []
for x in players.players:
dupes = MlbPlayer.select().where(
(MlbPlayer.key_fangraphs == x.key_fangraphs) | (MlbPlayer.key_mlbam == x.key_mlbam) |
(MlbPlayer.key_retro == x.key_retro) | (MlbPlayer.key_bbref == x.key_bbref)
)
if dupes.count() > 0:
db.close()
raise HTTPException(
status_code=400,
detail=f'{x.first_name} {x.last_name} has a key already in the database'
)
new_players.append(x.dict())
with db.atomic():
for batch in chunked(new_players, 15):
MlbPlayer.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_players)} new MLB players'
@router.delete('/{player_id}')
async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete mlb players. This event has been logged.'
)
this_player = MlbPlayer.get_or_none(MlbPlayer.id == player_id)
if this_player is None:
db.close()
raise HTTPException(status_code=404, detail=f'MlbPlayer id {player_id} not found')
count = this_player.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Player {player_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Player {player_id} was not deleted')

View File

@ -0,0 +1,201 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Notification, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/notifs',
tags=['notifs']
)
class NotifModel(pydantic.BaseModel):
created: int
title: str
desc: Optional[str] = None
field_name: str
message: str
about: Optional[str] = 'blank'
ack: Optional[bool] = False
@router.get('')
async def get_notifs(
created_after: Optional[int] = None, title: Optional[str] = None, desc: Optional[str] = None,
field_name: Optional[str] = None, in_desc: Optional[str] = None, about: Optional[str] = None,
ack: Optional[bool] = None, csv: Optional[bool] = None):
all_notif = Notification.select()
if all_notif.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no notifications to filter')
if created_after is not None:
all_notif = all_notif.where(Notification.created < created_after)
if title is not None:
all_notif = all_notif.where(Notification.title == title)
if desc is not None:
all_notif = all_notif.where(Notification.desc == desc)
if field_name is not None:
all_notif = all_notif.where(Notification.field_name == field_name)
if in_desc is not None:
all_notif = all_notif.where(fn.Lower(Notification.desc).contains(in_desc.lower()))
if about is not None:
all_notif = all_notif.where(Notification.about == about)
if ack is not None:
all_notif = all_notif.where(Notification.ack == ack)
if csv:
data_list = [['id', 'created', 'title', 'desc', 'field_name', 'message', 'about', 'ack']]
for line in all_notif:
data_list.append([
line.id, line.created, line.title, line.desc, line.field_name, line.message, line.about, line.ack
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_notif.count(), 'notifs': []}
for x in all_notif:
return_val['notifs'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{notif_id}')
async def get_one_notif(notif_id, csv: Optional[bool] = None):
try:
this_notif = Notification.get_by_id(notif_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No notification found with id {notif_id}')
if csv:
data_list = [
['id', 'created', 'title', 'desc', 'field_name', 'message', 'about', 'ack'],
[this_notif.id, this_notif.created, this_notif.title, this_notif.desc, this_notif.field_name,
this_notif.message, this_notif.about, this_notif.ack]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_notif)
db.close()
return return_val
@router.post('')
async def post_notif(notif: NotifModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post notifications. This event has been logged.'
)
logging.info(f'new notif: {notif}')
this_notif = Notification(
created=notif.created,
title=notif.title,
desc=notif.desc,
field_name=notif.field_name,
message=notif.message,
about=notif.about,
)
saved = this_notif.save()
if saved == 1:
return_val = model_to_dict(this_notif)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that notification'
)
@router.patch('/{notif_id}')
async def patch_notif(
notif_id, created: Optional[int] = None, title: Optional[str] = None, desc: Optional[str] = None,
field_name: Optional[str] = None, message: Optional[str] = None, about: Optional[str] = None,
ack: Optional[bool] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch notifications. This event has been logged.'
)
try:
this_notif = Notification.get_by_id(notif_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No notification found with id {notif_id}')
if title is not None:
this_notif.title = title
if desc is not None:
this_notif.desc = desc
if field_name is not None:
this_notif.field_name = field_name
if message is not None:
this_notif.message = message
if about is not None:
this_notif.about = about
if ack is not None:
this_notif.ack = ack
if created is not None:
this_notif.created = created
if this_notif.save() == 1:
return_val = model_to_dict(this_notif)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{notif_id}')
async def delete_notif(notif_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete notifications. This event has been logged.'
)
try:
this_notif = Notification.get_by_id(notif_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No notification found with id {notif_id}')
count = this_notif.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Notification {notif_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Notification {notif_id} was not deleted')

259
app/routers_v2/packs.py Normal file
View File

@ -0,0 +1,259 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional, List
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Cardset, model_to_dict, Pack, Team, PackType
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/packs',
tags=['packs']
)
class PackPydantic(pydantic.BaseModel):
team_id: int
pack_type_id: int
pack_team_id: Optional[int] = None
pack_cardset_id: Optional[int] = None
open_time: Optional[str] = None
class PackModel(pydantic.BaseModel):
packs: List[PackPydantic]
@router.get('')
async def get_packs(
team_id: Optional[int] = None, pack_type_id: Optional[int] = None, opened: Optional[bool] = None,
limit: Optional[int] = None, new_to_old: Optional[bool] = None, pack_team_id: Optional[int] = None,
pack_cardset_id: Optional[int] = None, exact_match: Optional[bool] = False, csv: Optional[bool] = None):
all_packs = Pack.select()
if all_packs.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no packs to filter')
if team_id is not None:
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
all_packs = all_packs.where(Pack.team == this_team)
if pack_type_id is not None:
try:
this_pack_type = PackType.get_by_id(pack_type_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No pack type found with id {pack_type_id}')
all_packs = all_packs.where(Pack.pack_type == this_pack_type)
if pack_team_id is not None:
try:
this_pack_team = Team.get_by_id(pack_team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {pack_team_id}')
all_packs = all_packs.where(Pack.pack_team == this_pack_team)
elif exact_match:
all_packs = all_packs.where(Pack.pack_team == None)
if pack_cardset_id is not None:
try:
this_pack_cardset = Cardset.get_by_id(pack_cardset_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No cardset found with id {pack_cardset_id}')
all_packs = all_packs.where(Pack.pack_cardset == this_pack_cardset)
elif exact_match:
all_packs = all_packs.where(Pack.pack_cardset == None)
if opened is not None:
all_packs = all_packs.where(Pack.open_time.is_null(not opened))
if limit is not None:
all_packs = all_packs.limit(limit)
if new_to_old is not None:
all_packs = all_packs.order_by(-Pack.id)
# if all_packs.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No packs found')
if csv:
data_list = [['id', 'team', 'pack_type', 'open_time']]
for line in all_packs:
data_list.append(
[
line.id, line.team.abbrev, line.pack_type.name,
datetime.fromtimestamp(line.open_time) if line.open_time else None
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_packs.count(), 'packs': []}
for x in all_packs:
return_val['packs'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{pack_id}')
async def get_one_pack(pack_id, csv: Optional[bool] = False):
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No pack found with id {pack_id}')
if csv:
data_list = [
['id', 'team', 'pack_type', 'open_time'],
[this_pack.id, this_pack.team.abbrev, this_pack.pack_type.name,
datetime.fromtimestamp(this_pack.open_time) if this_pack.open_time else None]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_pack)
db.close()
return return_val
@router.post('')
async def post_pack(packs: PackModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post packs. This event has been logged.'
)
new_packs = []
for x in packs.packs:
this_player = Pack(
team_id=x.team_id,
pack_type_id=x.pack_type_id,
pack_team_id=x.pack_team_id,
pack_cardset_id=x.pack_cardset_id,
open_time=x.open_time if x.open_time != "" else None
)
new_packs.append(this_player)
with db.atomic():
Pack.bulk_create(new_packs, batch_size=15)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_packs)} packs have been added')
@router.post('/one')
async def post_one_pack(pack: PackPydantic, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post packs. This event has been logged.'
)
this_pack = Pack(
team_id=pack.team_id,
pack_type_id=pack.pack_type_id,
pack_team_id=pack.pack_team_id,
pack_cardset_id=pack.pack_cardset_id,
open_time=pack.open_time
)
saved = this_pack.save()
if saved == 1:
return_val = model_to_dict(this_pack)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that cardset'
)
@router.patch('/{pack_id}')
async def patch_pack(
pack_id, team_id: Optional[int] = None, pack_type_id: Optional[int] = None, open_time: Optional[int] = None,
pack_team_id: Optional[int] = None, pack_cardset_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch packs. This event has been logged.'
)
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No pack found with id {pack_id}')
if team_id is not None:
this_pack.team_id = team_id
if pack_type_id is not None:
this_pack.pack_type_id = pack_type_id
if pack_team_id is not None:
this_pack.pack_team_id = pack_team_id
if pack_cardset_id is not None:
this_pack.pack_cardset_id = pack_cardset_id
if open_time is not None:
this_pack.open_time = open_time
if this_pack.save() == 1:
return_val = model_to_dict(this_pack)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{pack_id}')
async def delete_pack(pack_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete packs. This event has been logged.'
)
try:
this_pack = Pack.get_by_id(pack_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No packs found with id {pack_id}')
count = this_pack.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Pack {pack_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Pack {pack_id} was not deleted')

194
app/routers_v2/packtypes.py Normal file
View File

@ -0,0 +1,194 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, PackType, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/packtypes',
tags=['packtypes']
)
class PacktypeModel(pydantic.BaseModel):
name: str
card_count: int
description: str
cost: int
available: Optional[bool] = True
@router.get('')
async def get_packtypes(
name: Optional[str] = None, card_count: Optional[int] = None, in_desc: Optional[str] = None,
available: Optional[bool] = None, csv: Optional[bool] = None):
all_packtypes = PackType.select()
if all_packtypes.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no packtypes to filter')
if name is not None:
all_packtypes = all_packtypes.where(fn.Lower(PackType.name) == name.lower())
if card_count is not None:
all_packtypes = all_packtypes.where(PackType.card_count == card_count)
if in_desc is not None:
all_packtypes = all_packtypes.where(fn.Lower(PackType.description).contains(in_desc.lower()))
if available is not None:
all_packtypes = all_packtypes.where(PackType.available == available)
# if all_packtypes.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No packtypes found')
if csv:
data_list = [['id', 'name', 'card_count', 'description']]
for line in all_packtypes:
data_list.append(
[
line.id, line.name, line.card_count, line.description
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_packtypes.count(), 'packtypes': []}
for x in all_packtypes:
return_val['packtypes'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{packtype_id}')
async def get_one_packtype(packtype_id, csv: Optional[bool] = False):
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No packtype found with id {packtype_id}')
if csv:
data_list = [
['id', 'name', 'card_count', 'description'],
[this_packtype.id, this_packtype.name, this_packtype.card_count, this_packtype.description]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_packtype)
db.close()
return return_val
@router.post('')
async def post_packtypes(packtype: PacktypeModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post packtypes. This event has been logged.'
)
dupe_packtype = PackType.get_or_none(PackType.name == packtype.name)
if dupe_packtype:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a packtype using {packtype.name}')
this_packtype = PackType(
name=packtype.name,
card_count=packtype.card_count,
description=packtype.description,
cost=packtype.cost,
available=packtype.available
)
saved = this_packtype.save()
if saved == 1:
return_val = model_to_dict(this_packtype)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that cardset'
)
@router.patch('/{packtype_id}')
async def patch_packtype(
packtype_id, name: Optional[str] = None, card_count: Optional[int] = None, description: Optional[str] = None,
cost: Optional[int] = None, available: Optional[bool] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch packtypes. This event has been logged.'
)
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No packtype found with id {packtype_id}')
if name is not None:
this_packtype.name = name
if card_count is not None:
this_packtype.card_count = card_count
if description is not None:
this_packtype.description = description
if cost is not None:
this_packtype.cost = cost
if available is not None:
this_packtype.available = available
if this_packtype.save() == 1:
return_val = model_to_dict(this_packtype)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{packtype_id}')
async def delete_packtype(packtype_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete packtypes. This event has been logged.'
)
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No packtype found with id {packtype_id}')
count = this_packtype.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Packtype {packtype_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Packtype {packtype_id} was not deleted')

207
app/routers_v2/paperdex.py Normal file
View File

@ -0,0 +1,207 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Paperdex, model_to_dict, Player, Cardset, Team
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/paperdex',
tags=['paperdex']
)
class PaperdexModel(pydantic.BaseModel):
team_id: int
player_id: int
created: Optional[int] = int(datetime.timestamp(datetime.now())*1000)
@router.get('')
async def get_paperdex(
team_id: Optional[int] = None, player_id: Optional[int] = None, created_after: Optional[int] = None,
cardset_id: Optional[int] = None, created_before: Optional[int] = None, flat: Optional[bool] = False,
csv: Optional[bool] = None):
all_dex = Paperdex.select().join(Player).join(Cardset)
if all_dex.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no paperdex to filter')
if team_id is not None:
all_dex = all_dex.where(Paperdex.team_id == team_id)
if player_id is not None:
all_dex = all_dex.where(Paperdex.player_id == player_id)
if cardset_id is not None:
all_sets = Cardset.select().where(Cardset.id == cardset_id)
all_dex = all_dex.where(Paperdex.player.cardset.id == cardset_id)
if created_after is not None:
all_dex = all_dex.where(Paperdex.created >= created_after)
if created_before is not None:
all_dex = all_dex.where(Paperdex.created <= created_before)
# if all_dex.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No paperdex found')
if csv:
data_list = [['id', 'team_id', 'player_id', 'created']]
for line in all_dex:
data_list.append(
[
line.id, line.team.id, line.player.player_id, line.created
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_dex.count(), 'paperdex': []}
for x in all_dex:
return_val['paperdex'].append(model_to_dict(x, recurse=not flat))
db.close()
return return_val
@router.get('/{paperdex_id}')
async def get_one_paperdex(paperdex_id, csv: Optional[bool] = False):
try:
this_dex = Paperdex.get_by_id(paperdex_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No paperdex found with id {paperdex_id}')
if csv:
data_list = [
['id', 'team_id', 'player_id', 'created'],
[this_dex.id, this_dex.team.id, this_dex.player.id, this_dex.created]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_dex)
db.close()
return return_val
@router.post('')
async def post_paperdex(paperdex: PaperdexModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post paperdex. This event has been logged.'
)
dupe_dex = Paperdex.get_or_none(Paperdex.team_id == paperdex.team_id, Paperdex.player_id == paperdex.player_id)
if dupe_dex:
return_val = model_to_dict(dupe_dex)
db.close()
return return_val
this_dex = Paperdex(
team_id=paperdex.team_id,
player_id=paperdex.player_id,
created=paperdex.created
)
saved = this_dex.save()
if saved == 1:
return_val = model_to_dict(this_dex)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that dex'
)
@router.patch('/{paperdex_id}')
async def patch_paperdex(
paperdex_id, team_id: Optional[int] = None, player_id: Optional[int] = None, created: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch paperdex. This event has been logged.'
)
try:
this_dex = Paperdex.get_by_id(paperdex_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No paperdex found with id {paperdex_id}')
if team_id is not None:
this_dex.team_id = team_id
if player_id is not None:
this_dex.player_id = player_id
if created is not None:
this_dex.created = created
if this_dex.save() == 1:
return_val = model_to_dict(this_dex)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{paperdex_id}')
async def delete_paperdex(paperdex_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete rewards. This event has been logged.'
)
try:
this_dex = Paperdex.get_by_id(paperdex_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No paperdex found with id {paperdex_id}')
count = this_dex.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Paperdex {this_dex} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Paperdex {this_dex} was not deleted')
@router.post('/wipe-ai')
async def wipe_ai_paperdex(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='Unauthorized'
)
g_teams = Team.select().where(Team.abbrev.contains('Gauntlet'))
count = Paperdex.delete().where(Paperdex.team << g_teams).execute()
return f'Deleted {count} records'

View File

@ -0,0 +1,267 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import Literal, Optional, List
import logging
import pandas as pd
import pydantic
from pydantic import validator, root_validator
from ..db_engine import db, PitchingCardRatings, model_to_dict, chunked, PitchingCard, Player, query_to_csv, Team
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/pitchingcardratings',
tags=['pitchingcardratings']
)
class PitchingCardRatingsModel(pydantic.BaseModel):
pitchingcard_id: int
vs_hand: Literal['R', 'L', 'vR', 'vL']
homerun: float = 0.0
bp_homerun: float = 0.0
triple: float = 0.0
double_three: float = 0.0
double_two: float = 0.0
double_cf: float = 0.0
single_two: float = 0.0
single_one: float = 0.0
single_center: float = 0.0
bp_single: float = 0.0
hbp: float = 0.0
walk: float = 0.0
strikeout: float = 0.0
flyout_lf_b: float = 0.0
flyout_cf_b: float = 0.0
flyout_rf_b: float = 0.0
groundout_a: float = 0.0
groundout_b: float = 0.0
xcheck_p: float = 0.0
xcheck_c: float = 0.0
xcheck_1b: float = 0.0
xcheck_2b: float = 0.0
xcheck_3b: float = 0.0
xcheck_ss: float = 0.0
xcheck_lf: float = 0.0
xcheck_cf: float = 0.0
xcheck_rf: float = 0.0
avg: float = 0.0
obp: float = 0.0
slg: float = 0.0
@validator("avg", always=True)
def avg_validator(cls, v, values, **kwargs):
return (values['homerun'] + values['bp_homerun'] / 2 + values['triple'] + values['double_three'] +
values['double_two'] + values['double_cf'] + values['single_two'] + values['single_one'] +
values['single_center'] + values['bp_single'] / 2) / 108
@validator("obp", always=True)
def obp_validator(cls, v, values, **kwargs):
return ((values['hbp'] + values['walk']) / 108) + values['avg']
@validator("slg", always=True)
def slg_validator(cls, v, values, **kwargs):
return (values['homerun'] * 4 + values['bp_homerun'] * 2 + values['triple'] * 3 + values['double_three'] * 2 +
values['double_two'] * 2 + values['double_cf'] * 2 + values['single_two'] + values['single_one'] +
values['single_center'] + values['bp_single'] / 2) / 108
@root_validator
def validate_chance_total(cls, values):
total_chances = (
values['homerun'] + values['bp_homerun'] + values['triple'] + values['double_three'] +
values['double_two'] + values['double_cf'] + values['single_two'] + values['single_one'] +
values['single_center'] + values['bp_single'] + values['hbp'] + values['walk'] +
values['strikeout'] + values['flyout_lf_b'] + values['flyout_cf_b'] + values['flyout_rf_b'] +
values['groundout_a'] + values['groundout_b'] + values['xcheck_p'] + values['xcheck_c'] +
values['xcheck_1b'] + values['xcheck_2b'] + values['xcheck_3b'] + values['xcheck_ss'] +
values['xcheck_lf'] + values['xcheck_cf'] + values['xcheck_rf'])
if round(total_chances) != 108:
raise ValueError("Must have exactly 108 chances on the card")
return values
class RatingsList(pydantic.BaseModel):
ratings: List[PitchingCardRatingsModel]
@router.get('')
async def get_card_ratings(
pitchingcard_id: list = Query(default=None), vs_hand: Literal['R', 'L', 'vR', 'vL'] = None,
short_output: bool = False, csv: bool = False, cardset_id: list = Query(default=None),
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to pull card ratings.'
)
all_ratings = PitchingCardRatings.select()
if pitchingcard_id is not None:
all_ratings = all_ratings.where(PitchingCardRatings.pitchingcard_id << pitchingcard_id)
if vs_hand is not None:
all_ratings = all_ratings.where(PitchingCardRatings.vs_hand == vs_hand[-1])
if cardset_id is not None:
set_players = Player.select(Player.player_id).where(Player.cardset_id << cardset_id)
set_cards = PitchingCard.select(PitchingCard.id).where(PitchingCard.player << set_players)
all_ratings = all_ratings.where(PitchingCardRatings.pitchingcard << set_cards)
if csv:
return_val = query_to_csv(all_ratings)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_ratings.count(), 'ratings': [
model_to_dict(x, recurse=not short_output) for x in all_ratings
]}
db.close()
return return_val
@router.get('/scouting')
async def get_card_scouting(team_id: int, ts: str, cardset_id: list = Query(default=None)):
this_team = Team.get_or_none(Team.id == team_id)
logging.debug(f'Team: {this_team} / has_guide: {this_team.has_guide}')
if this_team is None or ts != this_team.team_hash() or this_team.has_guide != 1:
logging.warning(f'Team_id {team_id} attempted to pull ratings')
db.close()
return 'Your team does not have the ratings guide enabled. If you have purchased a copy ping Cal to ' \
'make sure it is enabled on your team. If you are interested you can pick it up here (thank you!): ' \
'https://ko-fi.com/manticorum/shop'
all_ratings = PitchingCardRatings.select()
if cardset_id is not None:
set_players = Player.select(Player.player_id).where(Player.cardset_id << cardset_id)
set_cards = PitchingCard.select(PitchingCard.id).where(PitchingCard.player << set_players)
all_ratings = all_ratings.where(PitchingCardRatings.pitchingcard << set_cards)
vl_query = all_ratings.where(PitchingCardRatings.vs_hand == 'L')
vr_query = all_ratings.where(PitchingCardRatings.vs_hand == 'R')
vl_vals = [model_to_dict(x) for x in vl_query]
for x in vl_vals:
x.update(x['pitchingcard'])
x['player_id'] = x['pitchingcard']['player']['player_id']
x['player_name'] = x['pitchingcard']['player']['p_name']
x['rarity'] = x['pitchingcard']['player']['rarity']['name']
x['cardset_id'] = x['pitchingcard']['player']['cardset']['id']
x['cardset_name'] = x['pitchingcard']['player']['cardset']['name']
del x['pitchingcard']
del x['player']
vr_vals = [model_to_dict(x) for x in vr_query]
for x in vr_vals:
x['player_id'] = x['pitchingcard']['player']['player_id']
del x['pitchingcard']
vl = pd.DataFrame(vl_vals)
vr = pd.DataFrame(vr_vals)
db.close()
output = pd.merge(vl, vr, on='player_id', suffixes=('_vl', '_vr'))
first = ['player_id', 'player_name', 'cardset_name', 'rarity', 'hand', 'variant']
exclude = first + ['id_vl', 'id_vr', 'vs_hand_vl', 'vs_hand_vr']
output = output[first + [col for col in output.columns if col not in exclude]].sort_values(by=['player_id'])
# output = output.sort_values(by=['player_id'])
return Response(content=pd.DataFrame(output).to_csv(index=False), media_type='text/csv')
@router.get('/{ratings_id}')
async def get_one_rating(ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to pull card ratings.'
)
this_rating = PitchingCardRatings.get_or_none(PitchingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(status_code=404, detail=f'PitchingCardRating id {ratings_id} not found')
r_data = model_to_dict(this_rating)
db.close()
return r_data
@router.get('/player/{player_id}')
async def get_player_ratings(player_id: int, variant: list = Query(default=None), short_output: bool = False):
all_cards = PitchingCard.select().where(PitchingCard.player_id == player_id).order_by(PitchingCard.variant)
if variant is not None:
all_cards = all_cards.where(PitchingCard.variant << variant)
all_ratings = PitchingCardRatings.select().where(PitchingCardRatings.pitchingcard << all_cards)
return_val = {'count': all_ratings.count(), 'ratings': [
model_to_dict(x, recurse=not short_output) for x in all_ratings
]}
db.close()
return return_val
@router.put('')
async def put_ratings(ratings: RatingsList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post card ratings.'
)
new_ratings = []
updates = 0
for x in ratings.ratings:
try:
PitchingCardRatings.get(
(PitchingCardRatings.pitchingcard_id == x.pitchingcard_id) & (PitchingCardRatings.vs_hand == x.vs_hand)
)
updates += PitchingCardRatings.update(x.dict()).where(
(PitchingCardRatings.pitchingcard_id == x.pitchingcard_id) & (PitchingCardRatings.vs_hand == x.vs_hand)
).execute()
except PitchingCardRatings.DoesNotExist:
new_ratings.append(x.dict())
with db.atomic():
for batch in chunked(new_ratings, 30):
PitchingCardRatings.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Updated ratings: {updates}; new ratings: {len(new_ratings)}'
@router.delete('/{ratings_id}')
async def delete_rating(
ratings_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post card ratings.'
)
this_rating = PitchingCardRatings.get_or_none(PitchingCardRatings.id == ratings_id)
if this_rating is None:
db.close()
raise HTTPException(status_code=404, detail=f'PitchingCardRating id {ratings_id} not found')
count = this_rating.delete_instance()
db.close()
if count == 1:
return f'Rating {this_rating} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Rating {this_rating} could not be deleted')

View File

@ -0,0 +1,218 @@
import random
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import Literal, Optional, List
import logging
import pydantic
from ..db_engine import db, PitchingCard, model_to_dict, chunked, Player, fn, MlbPlayer
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/pitchingcards',
tags=['pitchingcards']
)
class PitchingCardModel(pydantic.BaseModel):
player_id: int
variant: int = 0
balk: int = 0
wild_pitch: int = 0
hold: int = 0
starter_rating: int = 1
relief_rating: int = 0
closer_rating: int = None
batting: str = "#1WR-C"
offense_col: int = None
hand: Literal['R', 'L', 'S'] = 'R'
class PitchingCardList(pydantic.BaseModel):
cards: List[PitchingCardModel]
@router.get('')
async def get_pitching_cards(
player_id: list = Query(default=None), player_name: list = Query(default=None),
cardset_id: list = Query(default=None), short_output: bool = False, limit: Optional[int] = None):
all_cards = PitchingCard.select()
if player_id is not None:
all_cards = all_cards.where(PitchingCard.player_id << player_id)
if cardset_id is not None:
all_players = Player.select().where(Player.cardset_id << cardset_id)
all_cards = all_cards.where(PitchingCard.player << all_players)
if player_name is not None:
name_list = [x.lower() for x in player_name]
all_players = Player.select().where(fn.lower(Player.p_name) << name_list)
all_cards = all_cards.where(PitchingCard.player << all_players)
if limit is not None:
all_cards = all_cards.limit(limit)
return_val = {'count': all_cards.count(), 'cards': [
model_to_dict(x, recurse=not short_output) for x in all_cards
]}
db.close()
return return_val
@router.get('/{card_id}')
async def get_one_card(card_id: int):
this_card = PitchingCard.get_or_none(PitchingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(status_code=404, detail=f'PitchingCard id {card_id} not found')
r_card = model_to_dict(this_card)
db.close()
return r_card
@router.get('/player/{player_id}')
async def get_player_cards(player_id: int, variant: list = Query(default=None), short_output: bool = False):
all_cards = PitchingCard.select().where(PitchingCard.player_id == player_id).order_by(PitchingCard.variant)
if variant is not None:
all_cards = all_cards.where(PitchingCard.variant << variant)
return_val = {'count': all_cards.count(), 'cards': [
model_to_dict(x, recurse=not short_output) for x in all_cards
]}
db.close()
return return_val
@router.put('')
async def put_cards(cards: PitchingCardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post pitching cards. This event has been logged.'
)
new_cards = []
updates = 0
for x in cards.cards:
try:
old = PitchingCard.get(
(PitchingCard.player_id == x.player_id) & (PitchingCard.variant == x.variant)
)
if x.offense_col is None:
x.offense_col = old.offense_col
updates += PitchingCard.update(x.dict()).where(
(PitchingCard.player_id == x.player_id) & (PitchingCard.variant == x.variant)
).execute()
except PitchingCard.DoesNotExist:
if x.offense_col is None:
this_player = Player.get_or_none(Player.player_id == x.player_id)
mlb_player = MlbPlayer.get_or_none(MlbPlayer.key_bbref == this_player.bbref_id)
if mlb_player is not None:
logging.info(f'setting offense_col to {mlb_player.offense_col} for {this_player.p_name}')
x.offense_col = mlb_player.offense_col
else:
logging.info(f'randomly setting offense_col for {this_player.p_name}')
x.offense_col = random.randint(1, 3)
logging.debug(f'x.dict(): {x.dict()}')
new_cards.append(x.dict())
with db.atomic():
for batch in chunked(new_cards, 30):
PitchingCard.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Updated cards: {updates}; new cards: {len(new_cards)}'
@router.patch('/{card_id}')
async def patch_card(
card_id: int, balk: Optional[int] = None, wild_pitch: Optional[int] = None, hold: Optional[int] = None,
starter_rating: Optional[int] = None, relief_rating: Optional[int] = None, closer_rating: Optional[int] = None,
batting: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch pitching cards. This event has been logged.'
)
this_card = PitchingCard.get_or_none(PitchingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(status_code=404, detail=f'PitchingCard id {card_id} not found')
if balk is not None:
this_card.balk = balk
if wild_pitch is not None:
this_card.wild_pitch = wild_pitch
if hold is not None:
this_card.hold = hold
if starter_rating is not None:
this_card.starter_rating = starter_rating
if relief_rating is not None:
this_card.relief_rating = relief_rating
if closer_rating is not None:
this_card.closer_rating = closer_rating
if batting is not None:
this_card.batting = batting
if this_card.save() == 1:
return_val = model_to_dict(this_card)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that card'
)
@router.delete('/{card_id}')
async def delete_card(card_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete pitching cards. This event has been logged.'
)
this_card = PitchingCard.get_or_none(PitchingCard.id == card_id)
if this_card is None:
db.close()
raise HTTPException(status_code=404, detail=f'Pitching id {card_id} not found')
count = this_card.delete_instance()
db.close()
if count == 1:
return f'Card {this_card} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Card {this_card} could not be deleted')
@router.delete('')
async def delete_all_cards(token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete pitching cards. This event has been logged.'
)
d_query = PitchingCard.delete()
d_query.execute()
return f'Deleted {d_query.count()} pitching cards'

191
app/routers_v2/pitstats.py Normal file
View File

@ -0,0 +1,191 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional, List
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, PitchingStat, model_to_dict, Card, Player, Current
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/pitstats',
tags=['pitstats']
)
class PitStat(pydantic.BaseModel):
card_id: int
team_id: int
vs_team_id: int
roster_num: int
ip: float
hit: Optional[int] = 0
run: Optional[int] = 0
erun: Optional[int] = 0
so: Optional[int] = 0
bb: Optional[int] = 0
hbp: Optional[int] = 0
wp: Optional[int] = 0
balk: Optional[int] = 0
hr: Optional[int] = 0
ir: Optional[int] = 0
irs: Optional[int] = 0
gs: Optional[int] = 0
win: Optional[int] = 0
loss: Optional[int] = 0
hold: Optional[int] = 0
sv: Optional[int] = 0
bsv: Optional[int] = 0
week: int
season: int
created: Optional[int] = int(datetime.timestamp(datetime.now())*100000)
game_id: int
class PitchingStatModel(pydantic.BaseModel):
stats: List[PitStat]
@router.get('')
async def get_pit_stats(
card_id: int = None, player_id: int = None, team_id: int = None, vs_team_id: int = None, week: int = None,
season: int = None, week_start: int = None, week_end: int = None, created: int = None, gs: bool = None,
csv: bool = None):
all_stats = PitchingStat.select().join(Card).join(Player)
logging.debug(f'pit query:\n\n{all_stats}')
if season is not None:
all_stats = all_stats.where(PitchingStat.season == season)
else:
curr = Current.latest()
all_stats = all_stats.where(PitchingStat.season == curr.season)
if card_id is not None:
all_stats = all_stats.where(PitchingStat.card_id == card_id)
if player_id is not None:
all_stats = all_stats.where(PitchingStat.card.player.player_id == player_id)
if team_id is not None:
all_stats = all_stats.where(PitchingStat.team_id == team_id)
if vs_team_id is not None:
all_stats = all_stats.where(PitchingStat.vs_team_id == vs_team_id)
if week is not None:
all_stats = all_stats.where(PitchingStat.week == week)
if week_start is not None:
all_stats = all_stats.where(PitchingStat.week >= week_start)
if week_end is not None:
all_stats = all_stats.where(PitchingStat.week <= week_end)
if created is not None:
all_stats = all_stats.where(PitchingStat.created <= created)
if gs is not None:
all_stats = all_stats.where(PitchingStat.gs == 1 if gs else 0)
# if all_stats.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No pitching stats found')
if csv:
data_list = [['id', 'card_id', 'player_id', 'cardset', 'team', 'vs_team', 'ip', 'hit', 'run', 'erun', 'so', 'bb', 'hbp',
'wp', 'balk', 'hr', 'ir', 'irs', 'gs', 'win', 'loss', 'hold', 'sv', 'bsv', 'week', 'season',
'created', 'game_id', 'roster_num']]
for line in all_stats:
data_list.append(
[
line.id, line.card.id, line.card.player.player_id, line.card.player.cardset.name, line.team.abbrev,
line.vs_team.abbrev, line.ip, line.hit,
line.run, line.erun, line.so, line.bb, line.hbp, line.wp, line.balk, line.hr, line.ir, line.irs,
line.gs, line.win, line.loss, line.hold, line.sv, line.bsv, line.week, line.season, line.created,
line.game_id, line.roster_num
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_stats.count(), 'stats': []}
for x in all_stats:
return_val['stats'].append(model_to_dict(x, recurse=False))
db.close()
return return_val
@router.post('')
async def post_pitstat(stats: PitchingStatModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post stats. This event has been logged.'
)
new_stats = []
for x in stats.stats:
this_stat = PitchingStat(
card_id=x.card_id,
team_id=x.team_id,
vs_team_id=x.vs_team_id,
roster_num=x.roster_num,
ip=x.ip,
hit=x.hit,
run=x.run,
erun=x.erun,
so=x.so,
bb=x.bb,
hbp=x.hbp,
wp=x.wp,
balk=x.balk,
hr=x.hr,
ir=x.ir,
irs=x.irs,
gs=x.gs,
win=x.win,
loss=x.loss,
hold=x.hold,
sv=x.sv,
bsv=x.bsv,
week=x.week,
season=x.season,
created=x.created,
game_id=x.game_id
)
new_stats.append(this_stat)
with db.atomic():
PitchingStat.bulk_create(new_stats, batch_size=15)
db.close()
raise HTTPException(status_code=200, detail=f'{len(new_stats)} pitching lines have been added')
@router.delete('/{stat_id}')
async def delete_pitstat(stat_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete stats. This event has been logged.'
)
try:
this_stat = PitchingStat.get_by_id(stat_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No stat found with id {stat_id}')
count = this_stat.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Stat {stat_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Stat {stat_id} was not deleted')

708
app/routers_v2/players.py Normal file
View File

@ -0,0 +1,708 @@
import os.path
import base64
import pandas as pd
from fastapi import APIRouter, Depends, HTTPException, Request, Response, Query
from fastapi.responses import FileResponse
from fastapi.templating import Jinja2Templates
from html2image import Html2Image
from typing import Optional, List, Literal
import logging
import pydantic
from pandas import DataFrame
from ..card_creation import get_batter_card_data, get_pitcher_card_data
from ..db_engine import db, Player, model_to_dict, fn, chunked, Paperdex, Cardset, Rarity, BattingCard, \
BattingCardRatings, PitchingCard, PitchingCardRatings, CardPosition
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/players',
tags=['players']
)
templates = Jinja2Templates(directory="storage/templates")
class PlayerPydantic(pydantic.BaseModel):
player_id: int = None
p_name: str
cost: int
image: str
image2: Optional[str] = None
mlbclub: str
franchise: str
cardset_id: int
set_num: int
rarity_id: int
pos_1: str
pos_2: Optional[str] = None
pos_3: Optional[str] = None
pos_4: Optional[str] = None
pos_5: Optional[str] = None
pos_6: Optional[str] = None
pos_7: Optional[str] = None
pos_8: Optional[str] = None
headshot: Optional[str] = None
vanity_card: Optional[str] = None
strat_code: Optional[str] = None
bbref_id: Optional[str] = None
fangr_id: Optional[str] = None
description: str
quantity: Optional[int] = 999
class PlayerModel(pydantic.BaseModel):
players: List[PlayerPydantic]
@router.get('')
async def get_players(
name: Optional[str] = None, value: Optional[int] = None, min_cost: Optional[int] = None,
max_cost: Optional[int] = None, has_image2: Optional[bool] = None, mlbclub: Optional[str] = None,
franchise: Optional[str] = None, cardset_id: list = Query(default=None), rarity_id: list = Query(default=None),
pos_include: list = Query(default=None), pos_exclude: list = Query(default=None), has_headshot: Optional[bool] = None,
has_vanity_card: Optional[bool] = None, strat_code: Optional[str] = None, bbref_id: Optional[str] = None,
fangr_id: Optional[str] = None, inc_dex: Optional[bool] = True, in_desc: Optional[str] = None,
flat: Optional[bool] = False, sort_by: Optional[str] = False, cardset_id_exclude: list = Query(default=None),
limit: Optional[int] = None, csv: Optional[bool] = None, short_output: Optional[bool] = False):
all_players = Player.select()
if all_players.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no players to filter')
if name is not None:
all_players = all_players.where(fn.Lower(Player.p_name) == name.lower())
if value is not None:
all_players = all_players.where(Player.cost == value)
if min_cost is not None:
all_players = all_players.where(Player.cost >= min_cost)
if max_cost is not None:
all_players = all_players.where(Player.cost <= max_cost)
if has_image2 is not None:
all_players = all_players.where(Player.image2.is_null(not has_image2))
if mlbclub is not None:
all_players = all_players.where(fn.Lower(Player.mlbclub) == mlbclub.lower())
if franchise is not None:
all_players = all_players.where(fn.Lower(Player.franchise) == franchise.lower())
if cardset_id is not None:
all_players = all_players.where(Player.cardset_id << cardset_id)
if cardset_id_exclude is not None:
all_players = all_players.where(Player.cardset_id.not_in(cardset_id_exclude))
if rarity_id is not None:
all_players = all_players.where(Player.rarity_id << rarity_id)
if pos_include is not None:
p_list = [x.upper() for x in pos_include]
all_players = all_players.where(
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | (Player.pos_4 << p_list) |
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | (Player.pos_8 << p_list)
)
if has_headshot is not None:
all_players = all_players.where(Player.headshot.is_null(not has_headshot))
if has_vanity_card is not None:
all_players = all_players.where(Player.vanity_card.is_null(not has_vanity_card))
if strat_code is not None:
all_players = all_players.where(Player.strat_code == strat_code)
if bbref_id is not None:
all_players = all_players.where(Player.bbref_id == bbref_id)
if fangr_id is not None:
all_players = all_players.where(Player.fangr_id == fangr_id)
if in_desc is not None:
all_players = all_players.where(fn.Lower(Player.description).contains(in_desc.lower()))
if sort_by is not None:
if sort_by == 'cost-desc':
all_players = all_players.order_by(-Player.cost)
elif sort_by == 'cost-asc':
all_players = all_players.order_by(Player.cost)
elif sort_by == 'name-asc':
all_players = all_players.order_by(Player.p_name)
elif sort_by == 'name-desc':
all_players = all_players.order_by(-Player.p_name)
elif sort_by == 'rarity-desc':
all_players = all_players.order_by(Player.rarity)
elif sort_by == 'rarity-asc':
all_players = all_players.order_by(-Player.rarity)
final_players = []
# logging.info(f'pos_exclude: {type(pos_exclude)} - {pos_exclude} - is None: {pos_exclude is None}')
for x in all_players:
if pos_exclude is not None and set([x.upper() for x in pos_exclude]).intersection(x.get_all_pos()):
pass
else:
final_players.append(x)
if limit is not None and len(final_players) >= limit:
break
# if len(final_players) == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No players found')
if csv:
card_vals = [model_to_dict(x) for x in all_players]
db.close()
for x in card_vals:
x['player_name'] = x['p_name']
x['cardset_name'] = x['cardset']['name']
x['rarity'] = x['rarity']['name']
x['for_purchase'] = x['cardset']['for_purchase']
x['ranked_legal'] = x['cardset']['ranked_legal']
if x['player_name'] not in x['description']:
x['description'] = f'{x["description"]} {x["player_name"]}'
card_df = pd.DataFrame(card_vals)
output = card_df[[
'player_id', 'player_name', 'cost', 'image', 'image2', 'mlbclub', 'franchise', 'cardset_name', 'rarity',
'pos_1', 'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
'fangr_id', 'bbref_id', 'description', 'for_purchase', 'ranked_legal'
]]
return Response(content=pd.DataFrame(output).to_csv(index=False), media_type='text/csv')
# all_players.order_by(-Player.rarity.value, Player.p_name)
# data_list = [['id', 'name', 'value', 'image', 'image2', 'mlbclub', 'franchise', 'cardset', 'rarity', 'pos_1',
# 'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
# 'strat_code', 'bbref_id', 'description', 'for_purchase', 'ranked_legal']]
# for line in final_players:
# data_list.append(
# [
# line.player_id, line.p_name, line.cost, line.image, line.image2, line.mlbclub, line.franchise,
# line.cardset, line.rarity, line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5, line.pos_6,
# line.pos_7, line.pos_8, line.headshot, line.vanity_card, line.strat_code, line.bbref_id,
# line.description, line.cardset.for_purchase, line.cardset.ranked_legal
# # line.description, line.cardset.in_packs, line.quantity
# ]
# )
# return_val = DataFrame(data_list).to_csv(header=False, index=False)
#
# db.close()
# return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': len(final_players), 'players': []}
for x in final_players:
this_record = model_to_dict(x, recurse=not (flat or short_output))
if inc_dex:
this_dex = Paperdex.select().where(Paperdex.player == x)
this_record['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
for y in this_dex:
this_record['paperdex']['paperdex'].append(model_to_dict(y, recurse=False))
return_val['players'].append(this_record)
# return_val['players'].append(model_to_dict(x, recurse=not flat))
db.close()
return return_val
@router.get('/random')
async def get_random_player(
min_cost: Optional[int] = None, max_cost: Optional[int] = None, in_packs: Optional[bool] = None,
min_rarity: Optional[int] = None, max_rarity: Optional[int] = None, limit: Optional[int] = None,
pos_include: Optional[str] = None, pos_exclude: Optional[str] = None, franchise: Optional[str] = None,
mlbclub: Optional[str] = None, cardset_id: list = Query(default=None), pos_inc: list = Query(default=None),
pos_exc: list = Query(default=None), csv: Optional[bool] = None):
all_players = (Player
.select()
.join(Cardset)
.switch(Player)
.join(Rarity)
.order_by(fn.Random()))
if min_cost is not None:
all_players = all_players.where(Player.cost >= min_cost)
if max_cost is not None:
all_players = all_players.where(Player.cost <= max_cost)
if in_packs is not None:
if in_packs:
all_players = all_players.where(Player.cardset.in_packs)
if min_rarity is not None:
all_players = all_players.where(Player.rarity.value >= min_rarity)
if max_rarity is not None:
all_players = all_players.where(Player.rarity.value <= max_rarity)
if pos_include is not None:
all_players = all_players.where(
(fn.lower(Player.pos_1) == pos_include.lower()) | (fn.lower(Player.pos_2) == pos_include.lower()) |
(fn.lower(Player.pos_3) == pos_include.lower()) | (fn.lower(Player.pos_4) == pos_include.lower()) |
(fn.lower(Player.pos_5) == pos_include.lower()) | (fn.lower(Player.pos_6) == pos_include.lower()) |
(fn.lower(Player.pos_7) == pos_include.lower()) | (fn.lower(Player.pos_8) == pos_include.lower())
)
if franchise is not None:
all_players = all_players.where(fn.Lower(Player.franchise) == franchise.lower())
if mlbclub is not None:
all_players = all_players.where(fn.Lower(Player.mlbclub) == mlbclub.lower())
if cardset_id is not None:
all_players = all_players.where(Player.cardset_id << cardset_id)
if pos_inc is not None:
p_list = [x.upper() for x in pos_inc]
all_players = all_players.where(
(Player.pos_1 << p_list) | (Player.pos_2 << p_list) | (Player.pos_3 << p_list) | (Player.pos_4 << p_list) |
(Player.pos_5 << p_list) | (Player.pos_6 << p_list) | (Player.pos_7 << p_list) | (Player.pos_8 << p_list)
)
# if pos_exc is not None:
# p_list = [x.upper() for x in pos_exc]
# logging.info(f'starting query: {all_players}\n\np_list: {p_list}\n\n')
# all_players = all_players.where(
# Player.pos_1.not_in(p_list) & Player.pos_2.not_in(p_list) & Player.pos_3.not_in(p_list) &
# Player.pos_4.not_in(p_list) & Player.pos_5.not_in(p_list) & Player.pos_6.not_in(p_list) &
# Player.pos_7.not_in(p_list) & Player.pos_8.not_in(p_list)
# )
# logging.info(f'post pos query: {all_players}')
if pos_exclude is not None and pos_exc is None:
final_players = [x for x in all_players if pos_exclude not in x.get_all_pos()]
elif pos_exc is not None and pos_exclude is None:
final_players = []
p_list = [x.upper() for x in pos_exc]
for x in all_players:
if limit is not None and len(final_players) >= limit:
break
if not set(p_list).intersection(x.get_all_pos()):
final_players.append(x)
else:
final_players = all_players
if limit is not None:
final_players = final_players[:limit]
# if len(final_players) == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No players found')
if csv:
data_list = [['id', 'name', 'cost', 'image', 'image2', 'mlbclub', 'franchise', 'cardset', 'rarity', 'pos_1',
'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
'strat_code', 'bbref_id', 'description']]
for line in final_players:
data_list.append(
[
line.id, line.p_name, line.cost, line.image, line.image2,
line.mlbclub, line.franchise, line.cardset.name, line.rarity.name,
line.pos_1, line.pos_2, line.pos_3, line.pos_4, line.pos_5,
line.pos_6, line.pos_7, line.pos_8, line.headshot, line.vanity_card,
line.strat_code, line.bbref_id, line.description
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': len(final_players), 'players': []}
for x in final_players:
this_record = model_to_dict(x)
this_dex = Paperdex.select().where(Paperdex.player == x)
this_record['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
for y in this_dex:
this_record['paperdex']['paperdex'].append(model_to_dict(y, recurse=False))
return_val['players'].append(this_record)
# return_val['players'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{player_id}')
async def get_one_player(player_id, csv: Optional[bool] = False):
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
if csv:
data_list = [['id', 'name', 'cost', 'image', 'image2', 'mlbclub', 'franchise', 'cardset', 'rarity', 'pos_1',
'pos_2', 'pos_3', 'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'headshot', 'vanity_card',
'strat_code', 'bbref_id', 'description']]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
data_list.append(
[
this_player.id, this_player.p_name, this_player.cost, this_player.image, this_player.image2,
this_player.mlbclub, this_player.franchise, this_player.cardset.name, this_player.rarity.name,
this_player.pos_1, this_player.pos_2, this_player.pos_3, this_player.pos_4, this_player.pos_5,
this_player.pos_6, this_player.pos_7, this_player.pos_8, this_player.headshot, this_player.vanity_card,
this_player.strat_code, this_player.bbref_id, this_player.description
]
)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_player)
this_dex = Paperdex.select().where(Paperdex.player == this_player)
return_val['paperdex'] = {'count': this_dex.count(), 'paperdex': []}
for x in this_dex:
return_val['paperdex']['paperdex'].append(model_to_dict(x, recurse=False))
db.close()
return return_val
@router.get('/{player_id}/{card_type}card')
async def get_batter_card(
request: Request, player_id: int, card_type: Literal['batting', 'pitching'], variant: int = 0, d: str = None,
html: Optional[bool] = False):
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
if os.path.isfile(f'storage/cards/cardset-{this_player.cardset.id}/{player_id}-{d}-v{variant}.png') and html is False:
db.close()
return FileResponse(
path=f'storage/cards/cardset-{this_player.cardset.id}/{player_id}-{d}-v{variant}.png',
media_type='image/png'
)
all_pos = CardPosition.select().where(CardPosition.player == this_player).order_by(CardPosition.innings.desc())
if card_type == 'batting':
this_bc = BattingCard.get_or_none(BattingCard.player == this_player, BattingCard.variant == variant)
if this_bc is None:
raise HTTPException(status_code=404, detail=f'Batting card not found for id {player_id}, variant {variant}')
rating_vl = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bc, BattingCardRatings.vs_hand == 'L')
rating_vr = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bc, BattingCardRatings.vs_hand == 'R')
if None in [rating_vr, rating_vl]:
raise HTTPException(status_code=404, detail=f'Ratings not found for batting card {this_bc.id}')
card_data = get_batter_card_data(this_player, this_bc, rating_vl, rating_vr, all_pos)
if this_player.description in this_player.cardset.name:
card_data['cardset_name'] = this_player.cardset.name
else:
card_data['cardset_name'] = this_player.description
card_data['request'] = request
html_response = templates.TemplateResponse("player_card.html", card_data)
else:
this_pc = PitchingCard.get_or_none(PitchingCard.player == this_player, PitchingCard.variant == variant)
if this_pc is None:
raise HTTPException(
status_code=404, detail=f'Pitching card not found for id {player_id}, variant {variant}')
rating_vl = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard == this_pc, PitchingCardRatings.vs_hand == 'L')
rating_vr = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard == this_pc, PitchingCardRatings.vs_hand == 'R')
if None in [rating_vr, rating_vl]:
raise HTTPException(status_code=404, detail=f'Ratings not found for pitching card {this_pc.id}')
card_data = get_pitcher_card_data(this_player, this_pc, rating_vl, rating_vr, all_pos)
if this_player.description in this_player.cardset.name:
card_data['cardset_name'] = this_player.cardset.name
else:
card_data['cardset_name'] = this_player.description
card_data['request'] = request
html_response = templates.TemplateResponse("player_card.html", card_data)
if html:
db.close()
return html_response
updates = 0
if card_type == 'batting':
updates += BattingCardRatings.update(card_data['new_ratings_vl'].dict()).where(
(BattingCardRatings.id == rating_vl.id)
).execute()
updates += BattingCardRatings.update(card_data['new_ratings_vr'].dict()).where(
(BattingCardRatings.id == rating_vr.id)
).execute()
else:
updates += PitchingCardRatings.update(card_data['new_ratings_vl'].dict()).where(
(PitchingCardRatings.id == rating_vl.id)
).execute()
updates += PitchingCardRatings.update(card_data['new_ratings_vr'].dict()).where(
(PitchingCardRatings.id == rating_vr.id)
).execute()
logging.info(f'Rating updates: {updates}')
hti = Html2Image(
browser='chromium',
size=(1200, 600),
output_path=f'storage/cards/cardset-{this_player.cardset.id}/',
custom_flags=['--no-sandbox', '--disable-remote-debugging', '--headless', '--disable-gpu',
'--disable-software-rasterizer', '--disable-dev-shm-usage']
)
logging.debug(f'body:\n{html_response.body.decode("UTF-8")}')
x = hti.screenshot(
html_str=str(html_response.body.decode("UTF-8")),
save_as=f'{player_id}-{d}-v{variant}.png'
)
db.close()
return FileResponse(path=x[0], media_type='image/png')
# @router.get('/{player_id}/pitchingcard')
# async def get_pitcher_card(
# request: Request, player_id: int, variant: int = 0, d: str = None, html: Optional[bool] = False)
@router.patch('/{player_id}')
async def v1_players_patch(
player_id, name: Optional[str] = None, image: Optional[str] = None, image2: Optional[str] = None,
mlbclub: Optional[str] = None, franchise: Optional[str] = None, cardset_id: Optional[int] = None,
rarity_id: Optional[int] = None, pos_1: Optional[str] = None, pos_2: Optional[str] = None,
pos_3: Optional[str] = None, pos_4: Optional[str] = None, pos_5: Optional[str] = None,
pos_6: Optional[str] = None, pos_7: Optional[str] = None, pos_8: Optional[str] = None,
headshot: Optional[str] = None, vanity_card: Optional[str] = None, strat_code: Optional[str] = None,
bbref_id: Optional[str] = None, description: Optional[str] = None, cost: Optional[int] = None,
fangr_id: Optional[str] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch players. This event has been logged.'
)
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
if cost is not None:
this_player.cost = cost
if name is not None:
this_player.p_name = name
if image is not None:
this_player.image = image
if image2 is not None:
if image2.lower() == 'false':
this_player.image2 = None
else:
this_player.image2 = image2
if mlbclub is not None:
this_player.mlbclub = mlbclub
if franchise is not None:
this_player.franchise = franchise
if cardset_id is not None:
try:
this_cardset = Cardset.get_by_id(cardset_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No cardset found with id {cardset_id}')
this_player.cardset = this_cardset
if rarity_id is not None:
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
this_player.rarity = this_rarity
if pos_1 is not None:
if pos_1 in ['None', 'False', '']:
this_player.pos_1 = None
else:
this_player.pos_1 = pos_1
if pos_2 is not None:
if pos_2 in ['None', 'False', '']:
this_player.pos_2 = None
else:
this_player.pos_2 = pos_2
if pos_3 is not None:
if pos_3 in ['None', 'False', '']:
this_player.pos_3 = None
else:
this_player.pos_3 = pos_3
if pos_4 is not None:
if pos_4 in ['None', 'False', '']:
this_player.pos_4 = None
else:
this_player.pos_4 = pos_4
if pos_5 is not None:
if pos_5 in ['None', 'False', '']:
this_player.pos_5 = None
else:
this_player.pos_5 = pos_5
if pos_6 is not None:
if pos_6 in ['None', 'False', '']:
this_player.pos_6 = None
else:
this_player.pos_6 = pos_6
if pos_7 is not None:
if pos_7 in ['None', 'False', '']:
this_player.pos_7 = None
else:
this_player.pos_7 = pos_7
if pos_8 is not None:
if pos_8 in ['None', 'False', '']:
this_player.pos_8 = None
else:
this_player.pos_8 = pos_8
if headshot is not None:
this_player.headshot = headshot
if vanity_card is not None:
this_player.vanity_card = vanity_card
if strat_code is not None:
this_player.strat_code = strat_code
if bbref_id is not None:
this_player.bbref_id = bbref_id
if fangr_id is not None:
this_player.fangr_id = fangr_id
if description is not None:
this_player.description = description
if this_player.save() == 1:
return_val = model_to_dict(this_player)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.put('')
async def put_players(players: PlayerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post players. This event has been logged.'
)
new_players = []
for x in players.players:
# this_player = Player(
# player_id=x.player_id,
# p_name=x.p_name,
# cost=x.cost,
# image=x.image,
# image2=x.image2,
# mlbclub=x.mlbclub,
# franchise=x.franchise,
# cardset_id=x.cardset_id,
# rarity_id=x.rarity_id,
# set_num=x.set_num,
# pos_1=x.pos_1,
# pos_2=x.pos_2,
# pos_3=x.pos_3,
# pos_4=x.pos_4,
# pos_5=x.pos_5,
# pos_6=x.pos_6,
# pos_7=x.pos_7,
# pos_8=x.pos_8,
# headshot=x.headshot,
# vanity_card=x.vanity_card,
# strat_code=x.strat_code,
# fangr_id=x.fangr_id,
# bbref_id=x.bbref_id,
# description=x.description
# )
# new_players.append(this_player)
new_players.append({
'player_id': x.player_id,
'p_name': x.p_name,
'cost': x.cost,
'image': x.image,
'image2': x.image2,
'mlbclub': x.mlbclub.title(),
'franchise': x.franchise.title(),
'cardset_id': x.cardset_id,
'rarity_id': x.rarity_id,
'set_num': x.set_num,
'pos_1': x.pos_1,
'pos_2': x.pos_2,
'pos_3': x.pos_3,
'pos_4': x.pos_4,
'pos_5': x.pos_5,
'pos_6': x.pos_6,
'pos_7': x.pos_7,
'pos_8': x.pos_8,
'headshot': x.headshot,
'vanity_card': x.vanity_card,
'strat_code': x.strat_code,
'fangr_id': x.fangr_id,
'bbref_id': x.bbref_id,
'description': x.description
})
logging.info(f'new_players: {new_players}')
with db.atomic():
# Player.bulk_create(new_players, batch_size=15)
for batch in chunked(new_players, 15):
logging.info(f'batch: {batch}')
Player.insert_many(batch).on_conflict_replace().execute()
db.close()
# sheets.update_all_players(SHEETS_AUTH)
raise HTTPException(status_code=200, detail=f'{len(new_players)} players have been added')
@router.post('')
async def post_players(new_player: PlayerPydantic, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post players. This event has been logged.'
)
dupe_query = Player.select().where(
(Player.bbref_id == new_player.bbref_id) & (Player.cardset_id == new_player.cardset_id)
)
if dupe_query.count() != 0:
db.close()
raise HTTPException(
status_code=400,
detail=f'This appears to be a duplicate with player {dupe_query[0].player_id}'
)
p_query = Player.select(Player.player_id).order_by(-Player.player_id).limit(1)
new_id = p_query[0].player_id + 1
new_player.player_id = new_id
p_id = Player.insert(new_player.dict()).execute()
return_val = model_to_dict(Player.get_by_id(p_id))
db.close()
return return_val
@router.delete('/{player_id}')
async def delete_player(player_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete players. This event has been logged.'
)
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No player found with id {player_id}')
count = this_player.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Player {player_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Player {player_id} was not deleted')

187
app/routers_v2/rarity.py Normal file
View File

@ -0,0 +1,187 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Rarity, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/rarities',
tags=['rarities']
)
class RarityModel(pydantic.BaseModel):
value: int
name: str
color: str
@router.get('')
async def get_rarities(value: Optional[int] = None, name: Optional[str] = None, min_value: Optional[int] = None,
max_value: Optional[int] = None, csv: Optional[bool] = None):
all_rarities = Rarity.select()
if all_rarities.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no rarities to filter')
if value is not None:
all_rarities = all_rarities.where(Rarity.value == value)
if name is not None:
all_rarities = all_rarities.where(fn.Lower(Rarity.name) == name.lower())
if min_value is not None:
all_rarities = all_rarities.where(Rarity.value >= min_value)
if max_value is not None:
all_rarities = all_rarities.where(Rarity.value <= max_value)
if all_rarities.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No rarities found')
if csv:
data_list = [['id', 'value', 'name']]
for line in all_rarities:
data_list.append(
[
line.id, line.value, line.name
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_rarities.count(), 'rarities': []}
for x in all_rarities:
return_val['rarities'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{rarity_id}')
async def get_one_rarity(rarity_id, csv: Optional[bool] = False):
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
if csv:
data_list = [['id', 'value', 'name']]
for line in this_rarity:
data_list.append(
[
line.id, line.value, line.name
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_rarity)
db.close()
return return_val
@router.post('')
async def post_rarity(rarity: RarityModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post rarities. This event has been logged.'
)
dupe_team = Rarity.get_or_none(Rarity.name)
if dupe_team:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a rarity using {rarity.name}')
this_rarity = Rarity(
value=rarity.value,
name=rarity.name,
color=rarity.color
)
saved = this_rarity.save()
if saved == 1:
return_val = model_to_dict(this_rarity)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.patch('/{rarity_id}')
async def patch_rarity(
rarity_id, value: Optional[int] = None, name: Optional[str] = None, color: Optional[str] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch rarities. This event has been logged.'
)
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
if value is not None:
this_rarity.value = value
if name is not None:
this_rarity.name = name
if color is not None:
this_rarity.color = color
if this_rarity.save() == 1:
return_val = model_to_dict(this_rarity)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{rarity_id}')
async def v1_rarities_delete(rarity_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete rarities. This event has been logged.'
)
try:
this_rarity = Rarity.get_by_id(rarity_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No rarity found with id {rarity_id}')
count = this_rarity.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Rarity {rarity_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Rarity {rarity_id} was not deleted')

431
app/routers_v2/results.py Normal file
View File

@ -0,0 +1,431 @@
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Result, model_to_dict, Team, DataError
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/results',
tags=['results']
)
class ResultModel(pydantic.BaseModel):
away_team_id: int
home_team_id: int
away_score: int
home_score: int
away_team_value: Optional[int] = None
home_team_value: Optional[int] = None
away_team_ranking: Optional[int] = None
home_team_ranking: Optional[int] = None
scorecard: str
week: int
season: int
ranked: bool
short_game: bool
game_type: str
@router.get('')
async def get_results(
away_team_id: Optional[int] = None, home_team_id: Optional[int] = None, team_one_id: Optional[int] = None,
team_two_id: Optional[int] = None, away_score_min: Optional[int] = None, away_score_max: Optional[int] = None,
home_score_min: Optional[int] = None, home_score_max: Optional[int] = None, bothscore_min: Optional[int] = None,
bothscore_max: Optional[int] = None, season: Optional[int] = None, week: Optional[int] = None,
week_start: Optional[int] = None, week_end: Optional[int] = None, ranked: Optional[bool] = None,
short_game: Optional[bool] = None, game_type: Optional[str] = None, vs_ai: Optional[bool] = None,
csv: Optional[bool] = None):
all_results = Result.select()
# if all_results.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'There are no results to filter')
if away_team_id is not None:
try:
this_team = Team.get_by_id(away_team_id)
all_results = all_results.where(Result.away_team == this_team)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {away_team_id}')
if home_team_id is not None:
try:
this_team = Team.get_by_id(home_team_id)
all_results = all_results.where(Result.home_team == this_team)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {home_team_id}')
if team_one_id is not None:
try:
this_team = Team.get_by_id(team_one_id)
all_results = all_results.where((Result.home_team == this_team) | (Result.away_team == this_team))
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_one_id}')
if team_two_id is not None:
try:
this_team = Team.get_by_id(team_two_id)
all_results = all_results.where((Result.home_team == this_team) | (Result.away_team == this_team))
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_two_id}')
if away_score_min is not None:
all_results = all_results.where(Result.away_score >= away_score_min)
if away_score_max is not None:
all_results = all_results.where(Result.away_score <= away_score_max)
if home_score_min is not None:
all_results = all_results.where(Result.home_score >= home_score_min)
if home_score_max is not None:
all_results = all_results.where(Result.home_score <= home_score_max)
if bothscore_min is not None:
all_results = all_results.where((Result.home_score >= bothscore_min) & (Result.away_score >= bothscore_min))
if bothscore_max is not None:
all_results = all_results.where((Result.home_score <= bothscore_max) & (Result.away_score <= bothscore_max))
if season is not None:
all_results = all_results.where(Result.season == season)
if week is not None:
all_results = all_results.where(Result.week == week)
if ranked is not None:
all_results = all_results.where(Result.ranked == ranked)
if short_game is not None:
all_results = all_results.where(Result.short_game == short_game)
if week_start is not None:
all_results = all_results.where(Result.week >= week_start)
if week_end is not None:
all_results = all_results.where(Result.week <= week_end)
if game_type is not None:
all_results = all_results.where(Result.game_type == game_type)
all_results = all_results.order_by(Result.id)
# Not functional
# if vs_ai is not None:
# AwayTeam = Team.alias()
# all_results = all_results.join(
# Team, on=Result.home_team
# ).switch(Result).join(
# Team, on=(AwayTeam.id == Result.away_team).alias('a_team')
# )
#
# if vs_ai:
# all_results = all_results.where(
# (Result.home_team.is_ai == 1) | (Result.a_team.is_ai == 1)
# )
# else:
# all_results = all_results.where(
# (Result.home_team.is_ai == 0) & (Result.a_team.is_ai == 0)
# )
# logging.info(f'Result Query:\n\n{all_results}')
if csv:
data_list = [['id', 'away_abbrev', 'home_abbrev', 'away_score', 'home_score', 'away_tv', 'home_tv',
'game_type', 'season', 'week', 'short_game', 'ranked']]
for line in all_results:
data_list.append([
line.id, line.away_team.abbrev, line.home_team.abbrev, line.away_score, line.home_score,
line.away_team_value, line.home_team_value, line.game_type if line.game_type else 'minor-league',
line.season, line.week, line.short_game, line.ranked
])
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_results.count(), 'results': []}
for x in all_results:
return_val['results'].append(model_to_dict(x))
db.close()
return return_val
@router.get('/{result_id}')
async def get_one_results(result_id, csv: Optional[bool] = None):
try:
this_result = Result.get_by_id(result_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No result found with id {result_id}')
if csv:
data_list = [
['id', 'away_abbrev', 'home_abbrev', 'away_score', 'home_score', 'away_tv', 'home_tv', 'game_type',
'season', 'week', 'game_type'],
[this_result.id, this_result.away_team.abbrev, this_result.away_team.abbrev, this_result.away_score,
this_result.home_score, this_result.away_team_value, this_result.home_team_value,
this_result.game_type if this_result.game_type else 'minor-league',
this_result.season, this_result.week, this_result.game_type]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_result)
db.close()
return return_val
@router.get('/team/{team_id}')
async def get_team_results(
team_id: int, season: Optional[int] = None, week: Optional[int] = None, csv: Optional[bool] = False):
all_results = Result.select().where((Result.away_team_id == team_id) | (Result.home_team_id == team_id))
try:
this_team = Team.get_by_id(team_id)
except Exception as e:
logging.error(f'Unknown team id {team_id} trying to pull team results')
raise HTTPException(404, f'Team id {team_id} not found')
if season is not None:
all_results = all_results.where(Result.season == season)
else:
all_results = all_results.where(Result.season == this_team.season)
if week is not None:
all_results = all_results.where(Result.week == week)
r_wins, r_loss, c_wins, c_loss = 0, 0, 0, 0
for x in all_results:
if x.away_team_id == team_id:
if x.away_score > x.home_score:
if x.ranked:
r_wins += 1
else:
c_wins += 1
else:
if x.ranked:
r_loss += 1
else:
c_loss += 1
elif x.home_team_id == team_id:
if x.away_score > x.home_score:
if x.ranked:
r_loss += 1
else:
c_loss += 1
else:
if x.ranked:
r_wins += 1
else:
c_wins += 1
if csv:
data_list = [
['team_id', 'ranked_wins', 'ranked_losses', 'casual_wins', 'casual_losses', 'team_ranking'],
[team_id, r_wins, r_loss, c_wins, c_loss, this_team.ranking]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {
'team': model_to_dict(this_team),
'ranked_wins': r_wins,
'ranked_losses': r_loss,
'casual_wins': c_wins,
'casual_losses': c_loss,
}
db.close()
return return_val
@router.post('')
async def post_result(result: ResultModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post results. This event has been logged.'
)
this_result = Result(**result.__dict__)
saved = this_result.save()
if result.ranked:
if not result.away_team_ranking:
db.close()
error = f'Ranked game did not include away team ({result.away_team_id}) ranking.'
logging.error(error)
raise DataError(error)
if not result.home_team_ranking:
db.close()
error = f'Ranked game did not include home team ({result.home_team_id}) ranking.'
logging.error(error)
raise DataError(error)
k_value = 20 if result.short_game else 60
ratio = (result.home_team_ranking - result.away_team_ranking) / 400
exp_score = 1 / (1 + (10 ** ratio))
away_win = True if result.away_score > result.home_score else False
total_delta = k_value * exp_score
high_delta = total_delta * exp_score if exp_score > .5 else total_delta * (1 - exp_score)
low_delta = total_delta - high_delta
# exp_score > .5 means away team is favorite
if exp_score > .5 and away_win:
final_delta = low_delta
away_delta = low_delta * 3
home_delta = -low_delta
elif away_win:
final_delta = high_delta
away_delta = high_delta * 3
home_delta = -high_delta
elif exp_score <= .5 and not away_win:
final_delta = low_delta
away_delta = -low_delta
home_delta = low_delta * 3
elif not away_win:
final_delta = high_delta
away_delta = -high_delta
home_delta = high_delta * 3
else:
final_delta = 0
away_delta = 0
home_delta = 0
logging.debug(f'/results ranking deltas\n\nk_value: {k_value} / ratio: {ratio} / '
f'exp_score: {exp_score} / away_win: {away_win} / total_delta: {total_delta} / '
f'high_delta: {high_delta} / low_delta: {low_delta} / final_delta: {final_delta} / ')
away_team = Team.get_by_id(result.away_team_id)
away_team.ranking += away_delta
away_team.save()
logging.info(f'Just updated {away_team.abbrev} ranking to {away_team.ranking}')
home_team = Team.get_by_id(result.home_team_id)
home_team.ranking += home_delta
home_team.save()
logging.info(f'Just updated {home_team.abbrev} ranking to {home_team.ranking}')
if saved == 1:
return_val = model_to_dict(this_result)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that roster'
)
@router.patch('/{result_id}')
async def patch_result(
result_id, away_team_id: Optional[int] = None, home_team_id: Optional[int] = None,
away_score: Optional[int] = None, home_score: Optional[int] = None, away_team_value: Optional[int] = None,
home_team_value: Optional[int] = None, scorecard: Optional[str] = None, week: Optional[int] = None,
season: Optional[int] = None, short_game: Optional[bool] = None, game_type: Optional[str] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch results. This event has been logged.'
)
try:
this_result = Result.get_by_id(result_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No result found with id {result_id}')
if away_team_id is not None:
this_result.away_team_id = away_team_id
if home_team_id is not None:
this_result.home_team_id = home_team_id
if away_score is not None:
this_result.away_score = away_score
if home_score is not None:
this_result.home_score = home_score
if away_team_value is not None:
this_result.away_team_value = away_team_value
if home_team_value is not None:
this_result.home_team_value = home_team_value
if scorecard is not None:
this_result.scorecard = scorecard
if week is not None:
this_result.week = week
if season is not None:
this_result.season = season
if game_type is not None:
this_result.game_type = game_type
if short_game is not None:
if not short_game:
this_result.short_game = None
else:
this_result.short_game = short_game
if this_result.save() == 1:
return_val = model_to_dict(this_result)
db.close()
return return_val
else:
db.close()
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that event'
)
@router.delete('/{result_id}')
async def delete_result(result_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post results. This event has been logged.'
)
try:
this_result = Result.get_by_id(result_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No result found with id {result_id}')
count = this_result.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Result {result_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Result {result_id} was not deleted')

187
app/routers_v2/rewards.py Normal file
View File

@ -0,0 +1,187 @@
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response
from typing import Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Reward, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/rewards',
tags=['rewards']
)
class RewardModel(pydantic.BaseModel):
name: str
season: int
week: int
team_id: int
created: Optional[int] = int(datetime.timestamp(datetime.now())*1000)
@router.get('')
async def get_rewards(
name: Optional[str] = None, in_name: Optional[str] = None, team_id: Optional[int] = None,
season: Optional[int] = None, week: Optional[int] = None, created_after: Optional[int] = None,
flat: Optional[bool] = False, csv: Optional[bool] = None):
all_rewards = Reward.select()
if all_rewards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'There are no rewards to filter')
if name is not None:
all_rewards = all_rewards.where(fn.Lower(Reward.name) == name.lower())
if team_id is not None:
all_rewards = all_rewards.where(Reward.team_id == team_id)
if created_after is not None:
all_rewards = all_rewards.where(Reward.created >= created_after)
if in_name is not None:
all_rewards = all_rewards.where(fn.Lower(Reward.name).contains(in_name.lower()))
if season is not None:
all_rewards = all_rewards.where(Reward.season == season)
if week is not None:
all_rewards = all_rewards.where(Reward.week == week)
if all_rewards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No rewards found')
if csv:
data_list = [['id', 'name', 'team', 'daily', 'created']]
for line in all_rewards:
data_list.append(
[
line.id, line.name, line.team.id, line.daily, line.created
]
)
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = {'count': all_rewards.count(), 'rewards': []}
for x in all_rewards:
return_val['rewards'].append(model_to_dict(x, recurse=not flat))
db.close()
return return_val
@router.get('/{reward_id}')
async def get_one_reward(reward_id, csv: Optional[bool] = False):
try:
this_reward = Reward.get_by_id(reward_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No reward found with id {reward_id}')
if csv:
data_list = [
['id', 'name', 'card_count', 'description'],
[this_reward.id, this_reward.name, this_reward.team.id, this_reward.daily, this_reward.created]
]
return_val = DataFrame(data_list).to_csv(header=False, index=False)
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_val = model_to_dict(this_reward)
db.close()
return return_val
@router.post('')
async def post_rewards(reward: RewardModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post rewards. This event has been logged.'
)
this_reward = Reward(**reward.dict())
saved = this_reward.save()
if saved == 1:
return_val = model_to_dict(this_reward)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that cardset'
)
@router.patch('/{reward_id}')
async def patch_reward(
reward_id, name: Optional[str] = None, team_id: Optional[int] = None, created: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to patch rewards. This event has been logged.'
)
try:
this_reward = Reward.get_by_id(reward_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No reward found with id {reward_id}')
if name is not None:
this_reward.name = name
if team_id is not None:
this_reward.team_id = team_id
if created is not None:
this_reward.created = created
if this_reward.save() == 1:
return_val = model_to_dict(this_reward)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that rarity'
)
@router.delete('/{reward_id}')
async def delete_reward(reward_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete rewards. This event has been logged.'
)
try:
this_reward = Reward.get_by_id(reward_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No reward found with id {reward_id}')
count = this_reward.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Reward {reward_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Reward {reward_id} was not deleted')

102
app/routers_v2/scouting.py Normal file
View File

@ -0,0 +1,102 @@
import csv
from datetime import datetime
from fastapi import APIRouter, Depends, HTTPException, Response, Query
from typing import Optional
import logging
import pydantic
import pandas as pd
from ..db_engine import db, model_to_dict, fn, query_to_csv, complex_data_to_csv, Player, BattingCardRatings
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, int_timestamp
from ..player_scouting import get_player_ids
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/scouting',
tags=['scouting']
)
class BattingFiles(pydantic.BaseModel):
vl_basic: str = 'vl-basic.csv'
vl_rate: str = 'vl-rate.csv'
vr_basic: str = 'vr-basic.csv'
vr_rate: str = 'vr-rate.csv'
running: str = 'running.csv'
# def csv_file_to_dataframe(filename: str) -> pd.DataFrame | None:
# with open(filename, 'r', encoding='utf8') as file:
# reader = csv.reader(file)
#
# for row in reader:
@router.get('/playerkeys')
async def get_player_keys(player_id: list = Query(default=None)):
all_keys = []
for x in player_id:
this_player = Player.get_or_none(Player.player_id == x)
if this_player is not None:
this_keys = get_player_ids(this_player.bbref_id, id_type='bbref')
if this_keys is not None:
all_keys.append(this_keys)
return_val = {'count': len(all_keys), 'keys': [
dict(x) for x in all_keys
]}
db.close()
return return_val
@router.post('/live-update/batting')
def live_update_batting(files: BattingFiles, cardset_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to initiate live updates.'
)
data = {} # <fg id>: { 'vL': [combined vl stat data], 'vR': [combined vr stat data] }
for row in files.vl_basic:
if row['pa'] >= 20:
data[row['fgid']]['vL'] = row
for row in files.vl_rate:
if row['fgid'] in data.keys():
data[row['fgid']]['vL'].extend(row)
for row in files.vr_basic:
if row['pa'] >= 40 and row['fgid'] in data.keys():
data[row['fgid']]['vR'] = row
for row in files.vr_rate:
if row['fgid'] in data.keys():
data[row['fgid']]['vR'].extend(row)
for x in data.items():
pass
# Create BattingCardRating object for vL
# Create BattingCardRating object for vR
# Read running stats and create/update BattingCard object
return files.dict()
@router.post('/live-update/pitching')
def live_update_pitching(files: BattingFiles, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to initiate live updates.'
)
return files.dict()

178
app/routers_v2/stratgame.py Normal file
View File

@ -0,0 +1,178 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import Literal, Optional, List
import logging
import pandas as pd
import pydantic
from pydantic import validator, root_validator
from ..db_engine import db, StratGame, model_to_dict, chunked, PitchingCard, Player, query_to_csv, Team, fn
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/games',
tags=['games']
)
class GameModel(pydantic.BaseModel):
season: int
game_type: str
away_team_id: int
home_team_id: int
week: int = 1
away_score: int = 0
home_score: int = 0
away_team_value: int = None
home_team_value: int = None
away_team_ranking: int = None
home_team_ranking: int = None
ranked: bool = False
short_game: bool = False
forfeit: bool = False
class GameList(pydantic.BaseModel):
games: List[GameModel]
@router.get('')
async def get_games(
season: list = Query(default=None), forfeit: Optional[bool] = None, away_team_id: list = Query(default=None),
home_team_id: list = Query(default=None), team1_id: list = Query(default=None),
team2_id: list = Query(default=None), game_type: list = Query(default=None), ranked: Optional[bool] = None,
short_game: Optional[bool] = None, csv: Optional[bool] = False, short_output: bool = False):
all_games = StratGame.select()
if season is not None:
all_games = all_games.where(StratGame.season << season)
if forfeit is not None:
all_games = all_games.where(StratGame.forfeit == forfeit)
if away_team_id is not None:
all_games = all_games.where(StratGame.away_team_id << away_team_id)
if home_team_id is not None:
all_games = all_games.where(StratGame.home_team_id << home_team_id)
if team1_id is not None:
all_games = all_games.where(
(StratGame.away_team_id << team1_id) | (StratGame.home_team_id << team1_id)
)
if team2_id is not None:
all_games = all_games.where(
(StratGame.away_team_id << team2_id) | (StratGame.home_team_id << team2_id)
)
if game_type is not None:
g_list = [x.lower() for x in game_type]
all_games = all_games.where(fn.Lower(StratGame.game_type) << g_list)
if ranked is not None:
all_games = all_games.where(StratGame.ranked == ranked)
if short_game is not None:
all_games = all_games.where(StratGame.short_game == short_game)
if csv:
return_vals = [model_to_dict(x) for x in all_games]
for x in return_vals:
x['away_abbrev'] = x['away_team']['abbrev']
x['home_abbrev'] = x['home_team']['abbrev']
del x['away_team'], x['home_team']
db.close()
output = pd.DataFrame(return_vals)[[
'id', 'away_abbrev', 'home_abbrev', 'away_score', 'home_score', 'away_team_value', 'home_team_value',
'game_type', 'season', 'week', 'short_game', 'ranked'
]]
return Response(content=output.to_csv(index=False), media_type='text/csv')
return_val = {'count': all_games.count(), 'games': [
model_to_dict(x, recurse=not short_output) for x in all_games
]}
db.close()
return return_val
@router.get('/{game_id}')
async def get_one_game(game_id: int):
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f'StratGame ID {game_id} not found')
g_result = model_to_dict(this_game)
db.close()
return g_result
@router.patch('/{game_id}')
async def patch_game(
game_id: int, game_type: Optional[str] = None, away_score: Optional[int] = None,
home_score: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_game - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f'StratGame ID {game_id} not found')
if away_score is not None:
this_game.away_score = away_score
if home_score is not None:
this_game.home_score = home_score
if game_type is not None:
this_game.game_type = game_type
if this_game.save() == 1:
g_result = model_to_dict(this_game)
db.close()
return g_result
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch game {game_id}')
@router.post('')
async def post_game(this_game: GameModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_games - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_game = StratGame(**this_game.dict())
saved = this_game.save()
if saved == 1:
return_val = model_to_dict(this_game)
db.close()
return return_val
else:
raise HTTPException(
status_code=418,
detail='Well slap my ass and call me a teapot; I could not save that game'
)
@router.delete('/{game_id}')
async def delete_game(game_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_game - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_game = StratGame.get_or_none(StratGame.id == game_id)
if not this_game:
db.close()
raise HTTPException(status_code=404, detail=f'StratGame ID {game_id} not found')
count = this_game.delete_instance()
db.close()
if count == 1:
return f'StratGame {game_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'StratGame {game_id} could not be deleted')

1054
app/routers_v2/stratplays.py Normal file

File diff suppressed because it is too large Load Diff

997
app/routers_v2/teams.py Normal file
View File

@ -0,0 +1,997 @@
from datetime import datetime
import pandas as pd
from fastapi import APIRouter, Depends, HTTPException, Response, Query
from typing import Optional, Literal
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Team, model_to_dict, fn, Pack, Card, Player, Paperdex, Notification, PackType, \
Rarity, Current, query_to_csv, complex_data_to_csv, CARDSETS, CardPosition, BattingCardRatings, BattingCard, \
PitchingCard, PitchingCardRatings
from ..dependencies import oauth2_scheme, valid_token, LOG_DATA, int_timestamp
logging.basicConfig(
filename=LOG_DATA['filename'],
format=LOG_DATA['format'],
level=LOG_DATA['log_level']
)
router = APIRouter(
prefix='/api/v2/teams',
tags=['teams']
)
class TeamModel(pydantic.BaseModel):
abbrev: str
sname: str
lname: str
gmid: int
gmname: str
wallet: int = 0
gsheet: str
team_value: int = 0
collection_value: int = 0
logo: Optional[str] = None
color: Optional[str] = None
season: int
ps_shiny: Optional[int] = 0
ranking: Optional[int] = 1000
has_guide: Optional[bool] = False
is_ai: Optional[bool] = False
@router.get('')
async def get_teams(
season: Optional[int] = None, gm_id: Optional[int] = None, abbrev: Optional[str] = None,
tv_min: Optional[int] = None, tv_max: Optional[int] = None, cv_min: Optional[int] = None,
cv_max: Optional[int] = None, ps_shiny_min: Optional[int] = None, ps_shiny_max: Optional[int] = None,
ranking_min: Optional[int] = None, ranking_max: Optional[int] = None, has_guide: Optional[bool] = None,
sname: Optional[str] = None, lname: Optional[str] = None, is_ai: Optional[bool] = None,
event_id: Optional[int] = None, limit: Optional[int] = None, csv: Optional[bool] = False):
"""
Param: season: int
Param: team_abbrev: string
Param: owner_id: int
"""
if season:
all_teams = Team.select_season(season)
else:
all_teams = Team.select()
# if all_teams.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'There are no teams to filter')
if gm_id is not None:
all_teams = all_teams.where(Team.gmid == gm_id)
if abbrev is not None:
all_teams = all_teams.where(fn.Lower(Team.abbrev) == abbrev.lower())
if sname is not None:
all_teams = all_teams.where(fn.Lower(Team.sname) == sname.lower())
if lname is not None:
all_teams = all_teams.where(fn.Lower(Team.lname) == lname.lower())
if tv_min is not None:
all_teams = all_teams.where(Team.team_value >= tv_min)
if tv_max is not None:
all_teams = all_teams.where(Team.team_value <= tv_max)
if cv_min is not None:
all_teams = all_teams.where(Team.collection_value >= cv_min)
if cv_max is not None:
all_teams = all_teams.where(Team.collection_value <= cv_max)
if ps_shiny_min is not None:
all_teams = all_teams.where(Team.career >= ps_shiny_min)
if ps_shiny_max is not None:
all_teams = all_teams.where(Team.career <= ps_shiny_max)
if ranking_min is not None:
all_teams = all_teams.where(Team.ranking >= ranking_min)
if ranking_max is not None:
all_teams = all_teams.where(Team.ranking <= ranking_max)
if ranking_max is not None:
all_teams = all_teams.where(Team.ranking <= ranking_max)
if has_guide is not None:
if not has_guide:
all_teams = all_teams.where(Team.has_guide == 0)
else:
all_teams = all_teams.where(Team.has_guide == 1)
if is_ai is not None:
all_teams = all_teams.where(Team.is_ai)
if event_id is not None:
all_teams = all_teams.where(Team.event_id == event_id)
if limit is not None:
all_teams = all_teams.limit(limit)
if csv:
return_val = query_to_csv(all_teams, exclude=[Team.career])
db.close()
return Response(content=return_val, media_type='text/csv')
else:
return_teams = {'count': all_teams.count(), 'teams': []}
for x in all_teams:
return_teams['teams'].append(model_to_dict(x))
db.close()
return return_teams
@router.get('/{team_id}')
async def get_one_team(team_id, inc_packs: bool = True, csv: Optional[bool] = False):
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
p_query = Pack.select().where((Pack.team == this_team) & (Pack.open_time.is_null(True)))
if csv:
data = model_to_dict(this_team)
data['sealed_packs'] = p_query.count()
return_val = complex_data_to_csv([data])
else:
return_val = model_to_dict(this_team)
if inc_packs:
return_val['sealed_packs'] = [model_to_dict(x) for x in p_query]
db.close()
return return_val
@router.get('/{team_id}/lineup/{difficulty_name}')
async def get_team_lineup(team_id: int, difficulty_name: str, pitcher_name: str, d_rank: int = 5, o_rank: int = 5):
"""
d_rank: int - 10: best overall, 9: prioritize range, 8: prioritize error
"""
this_team = Team.get_or_none(Team.id == team_id)
if this_team is None:
db.close()
raise HTTPException(status_code=404, detail=f'Team id {team_id} not found')
if difficulty_name not in CARDSETS.keys():
db.close()
raise HTTPException(status_code=400, detail=f'Difficulty name {difficulty_name} not a valid check')
# all_players = Player.select().where(
# (fn.Lower(Player.p_name) != pitcher_name.lower()) & (Player.mlbclub == this_team.lname)
# )
all_players = Player.select().where(Player.mlbclub == this_team.lname)
legal_players = all_players.where(Player.cardset_id << CARDSETS[difficulty_name]['primary'])
if 'secondary' in CARDSETS[difficulty_name]:
backup_players = all_players.where(Player.cardset_id << CARDSETS[difficulty_name]['secondary'])
else:
backup_players = None
logging.info(f'legal_players: {legal_players.count()}')
if backup_players is not None:
logging.info(f'backup_players: {backup_players.count()}')
player_names = []
starting_nine = {
'C': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'1B': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'2B': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'3B': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'SS': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'LF': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'CF': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'RF': {'player': None, 'vl': None, 'vr': None, 'ops': 0},
'DH': {'player': None, 'vl': None, 'vr': None, 'ops': 0}
}
def get_bratings(player_id):
this_bcard = BattingCard.get_or_none(BattingCard.player_id == player_id)
vl_ratings = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bcard, BattingCardRatings.vs_hand == 'L'
)
vl_ops = vl_ratings.obp + vl_ratings.slg
vr_ratings = BattingCardRatings.get_or_none(
BattingCardRatings.battingcard == this_bcard, BattingCardRatings.vs_hand == 'R'
)
vr_ops = vr_ratings.obp + vr_ratings.slg
return model_to_dict(vl_ratings), model_to_dict(vr_ratings), (vl_ops + vr_ops + min(vl_ops, vr_ops)) / 3
# IDEA: Rank guys by their bat per-position and take the best one that meets a threshold of defensive ability
for position in starting_nine.keys():
if position == 'DH':
# all_bcards = BattingCard.select().where(BattingCard.player << legal_players)
# all_batters = BattingCardRatings.select().where(
# BattingCardRatings.battingcard << all_bcards
# ).order_by(BattingCardRatings.obp + BattingCardRatings.sl)
#
# for x in all_batters:
# if x.battingcard.player.p_name not in player_names:
# starting_nine['DH'] = x.battingcard.player
# break
logging.info(f'Searching for a DH!')
dh_query = legal_players.order_by(Player.cost.desc())
for x in dh_query:
logging.info(f'checking {x.p_name} for {position}')
if x.p_name not in player_names and 'P' not in x.pos_1:
logging.info(f'adding!')
starting_nine['DH']['player'] = model_to_dict(x)
try:
vl, vr, total_ops = get_bratings(x.player_id)
except AttributeError as e:
logging.info(f'Could not find batting lines')
else:
starting_nine['DH']['vl'] = vl
starting_nine['DH']['vr'] = vr
starting_nine['DH']['ops'] = total_ops
player_names.append(x.p_name)
break
if starting_nine['DH']['player'] is None:
dh_query = backup_players.order_by(Player.cost.desc())
for x in dh_query:
logging.info(f'checking {x.p_name} for {position}')
if x.p_name not in player_names:
logging.info(f'adding!')
starting_nine['DH']['player'] = model_to_dict(x)
try:
vl, vr, total_ops = get_bratings(x.player_id)
except AttributeError as e:
logging.info(f'Could not find batting lines')
else:
vl, vr, total_ops = get_bratings(x.player_id)
starting_nine['DH']['vl'] = vl
starting_nine['DH']['vr'] = vr
starting_nine['DH']['ops'] = total_ops
player_names.append(x.p_name)
break
else:
pos_group = CardPosition.select().where(
(CardPosition.position == position) & (CardPosition.player << legal_players)
)
backup_group = CardPosition.select().where(
(CardPosition.position == position) & (CardPosition.player << backup_players)
)
if difficulty_name in ['minor-league', 'gauntlet-3']:
pos_group = pos_group.order_by(CardPosition.innings.desc())
elif d_rank == 10:
pos_group = pos_group.order_by((CardPosition.range * 5) + CardPosition.error)
elif d_rank == 9:
pos_group = pos_group.order_by(CardPosition.range)
elif d_rank == 8:
pos_group = pos_group.order_by(CardPosition.error.desc())
logging.info(f'pos_group: {pos_group}\n{starting_nine}\n{player_names}\n\n')
if difficulty_name in ['minor-league', 'gauntlet-3']:
for x in pos_group:
logging.info(f'checking {x.player.p_name} for {position}')
if x.player.p_name not in player_names and x.player.p_name.lower() != pitcher_name:
logging.info(f'adding!')
starting_nine[position]['player'] = model_to_dict(x.player)
vl, vr, total_ops = get_bratings(x.player.player_id)
starting_nine[position]['vl'] = vl
starting_nine[position]['vr'] = vr
starting_nine[position]['ops'] = total_ops
player_names.append(x.player.p_name)
break
if starting_nine[position]['player'] is None:
for x in backup_group:
logging.info(f'checking {x.player.p_name} for {position}')
if x.player.p_name not in player_names and x.player.p_name.lower() != pitcher_name:
logging.info(f'adding!')
starting_nine[position]['player'] = model_to_dict(x.player)
vl, vr, total_ops = get_bratings(x.player.player_id)
starting_nine[position]['vl'] = vl
starting_nine[position]['vr'] = vr
starting_nine[position]['ops'] = total_ops
player_names.append(x.player.p_name)
break
# all_bcards = BattingCard.select().where(BattingCard.player << starting_nine.values())
# all_ratings = BattingCardRatings.select().where(BattingCardRatings.battingcard << all_bcards)
#
# vl_query = all_ratings.where(BattingCardRatings.vs_hand == 'L')
# vr_query = all_ratings.where(BattingCardRatings.vs_hand == 'R')
#
# vl_vals = [model_to_dict(x) for x in vl_query]
# for x in vl_vals:
# x.update(x['battingcard'])
# x['player_id'] = x['battingcard']['player']['player_id']
# x['player_name'] = x['battingcard']['player']['p_name']
# x['rarity'] = x['battingcard']['player']['rarity']['name']
# x['cardset_id'] = x['battingcard']['player']['cardset']['id']
# x['cardset_name'] = x['battingcard']['player']['cardset']['name']
# del x['player']
#
# vr_vals = [model_to_dict(x) for x in vr_query]
# for x in vr_vals:
# x['player_id'] = x['battingcard']['player']['player_id']
# del x['battingcard']
#
# vl = pd.DataFrame(vl_vals)
# vr = pd.DataFrame(vr_vals)
# db.close()
#
# output = pd.merge(vl, vr, on='player_id', suffixes=('_vl', '_vr'))
#
# def get_total_ops(df_data):
# ops_vl = df_data['obp_vL'] + df_data['slg_vL']
# ops_vr = df_data['obp_vR'] + df_data['slg_vR']
# return (ops_vr + ops_vl + min(ops_vl, ops_vr)) / 3
# output['total_OPS'] = output.apply(get_total_ops, axis=1)
# output = output.sort_values(by=['total_OPS'], ascending=False)
sorted_nine = sorted(starting_nine.items(), key=lambda item: item[1]['ops'], reverse=True)
return {
'json': dict(sorted_nine),
'array': sorted_nine
}
def sort_pitchers(pitching_card_query) -> DataFrame | None:
all_s = [model_to_dict(x, recurse=False) for x in pitching_card_query]
if len(all_s) == 0:
logging.error(f'Empty pitching_card_query: {pitching_card_query}')
return None
pitcher_df = pd.DataFrame(all_s).set_index('player', drop=False)
logging.info(f'pitcher_df: {pitcher_df}')
def get_total_ops(df_data):
vlval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data['id'], PitchingCardRatings.vs_hand == 'L')
vrval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data['id'], PitchingCardRatings.vs_hand == 'R')
ops_vl = vlval.obp + vlval.slg
ops_vr = vrval.obp + vrval.slg
return (ops_vr + ops_vl + min(ops_vl, ops_vr)) / 3
pitcher_df['total_ops'] = pitcher_df.apply(get_total_ops, axis=1)
return pitcher_df.sort_values(by='total_ops')
@router.get('/{team_id}/sp/{difficulty_name}')
async def get_team_sp(team_id: int, difficulty_name: str, sp_rank: int):
logging.info(f'get_team_sp - team_id: {team_id} / difficulty_name: {difficulty_name} / sp_rank: {sp_rank}')
this_team = Team.get_or_none(Team.id == team_id)
if this_team is None:
db.close()
raise HTTPException(status_code=404, detail=f'Team id {team_id} not found')
if difficulty_name not in CARDSETS.keys():
db.close()
raise HTTPException(status_code=400, detail=f'Difficulty name {difficulty_name} not a valid check')
all_players = Player.select().where(Player.mlbclub == this_team.lname)
legal_players = all_players.where(Player.cardset_id << CARDSETS[difficulty_name]['primary'])
if 'secondary' in CARDSETS[difficulty_name]:
backup_players = all_players.where(Player.cardset_id << CARDSETS[difficulty_name]['secondary'])
else:
backup_players = None
def sort_starters(starter_query) -> DataFrame | None:
all_s = [model_to_dict(x, recurse=False) for x in starter_query]
if len(all_s) == 0:
logging.error(f'Empty starter_query: {starter_query}')
return None
starter_df = pd.DataFrame(all_s).set_index('player', drop=False)
logging.debug(f'starter_df: {starter_df}')
def get_total_ops(df_data):
vlval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data['id'], PitchingCardRatings.vs_hand == 'L')
vrval = PitchingCardRatings.get_or_none(
PitchingCardRatings.pitchingcard_id == df_data['id'], PitchingCardRatings.vs_hand == 'R')
ops_vl = vlval.obp + vlval.slg
ops_vr = vrval.obp + vrval.slg
return (ops_vr + ops_vl + min(ops_vl, ops_vr)) / 3
starter_df['total_ops'] = starter_df.apply(get_total_ops, axis=1)
return starter_df.sort_values(by='total_ops')
# Find SP in primary cardsets
s_query = PitchingCard.select().join(Player).where(
(PitchingCard.player << legal_players) & (PitchingCard.starter_rating >= 4)
)
all_starters = sort_starters(s_query)
logging.debug(f'sorted: {all_starters}')
if all_starters is not None and len(all_starters.index) >= sp_rank:
this_player_id = all_starters.iloc[sp_rank - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
if all_starters is not None and len(all_starters.index) > 0:
this_player_id = all_starters.iloc[len(all_starters.index) - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
# Include backup cardsets
s_query = PitchingCard.select().where(
(PitchingCard.player << backup_players) & (PitchingCard.starter_rating >= 4)
)
all_starters = sort_starters(s_query)
logging.debug(f'sorted: {all_starters}')
if all_starters is not None and len(all_starters.index) >= sp_rank:
this_player_id = all_starters.iloc[sp_rank - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
if all_starters is not None and len(all_starters.index) > 0:
this_player_id = all_starters.iloc[len(all_starters.index) - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
raise HTTPException(status_code=500, detail=f'No SP #{sp_rank} found for Team {team_id}')
@router.get('/{team_id}/rp/{difficulty_name}')
async def get_team_rp(
team_id: int, difficulty_name: str, need: Literal['length', 'setup', 'closer', 'middle'],
used_pitcher_ids: list = Query(default=[])):
logging.info(f'get_team_rp - team_id: {team_id} / difficulty_name: {difficulty_name} / need: {need} '
f'/ used_pitcher_ids: {used_pitcher_ids}')
this_team = Team.get_or_none(Team.id == team_id)
if this_team is None:
db.close()
raise HTTPException(status_code=404, detail=f'Team id {team_id} not found')
if difficulty_name not in CARDSETS.keys():
db.close()
raise HTTPException(status_code=400, detail=f'Difficulty name {difficulty_name} not a valid check')
all_players = Player.select().where(
(Player.mlbclub == this_team.lname) & (Player.player_id.not_in(used_pitcher_ids))
)
legal_players = all_players.where(Player.cardset_id << CARDSETS[difficulty_name]['primary'])
if 'secondary' in CARDSETS[difficulty_name]:
backup_players = all_players.where(Player.cardset_id << CARDSETS[difficulty_name]['secondary'])
else:
backup_players = None
if need == 'closer':
for query in [PitchingCard.select().join(Player).where(
(PitchingCard.player << legal_players) & (PitchingCard.closer_rating >= 3) &
(PitchingCard.starter_rating == 1)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << legal_players) & (PitchingCard.closer_rating >= 1) &
(PitchingCard.starter_rating == 1)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << backup_players) & (PitchingCard.closer_rating >= 3) &
(PitchingCard.starter_rating == 1)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << backup_players) & (PitchingCard.closer_rating >= 1) &
(PitchingCard.starter_rating == 1)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << backup_players) & (PitchingCard.starter_rating < 4))
]:
all_relievers = sort_pitchers(query)
if all_relievers is not None:
this_player_id = all_relievers.iloc[0].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
elif need == 'setup':
for query in [PitchingCard.select().join(Player).where(
(PitchingCard.player << legal_players) & (PitchingCard.starter_rating == 1)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << backup_players) & (PitchingCard.starter_rating < 4))
]:
all_relievers = sort_pitchers(query)
if all_relievers is not None and len(all_relievers.index) >= 2:
this_player_id = all_relievers.iloc[1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
elif need == 'length' or len(used_pitcher_ids) > 4:
for query in [PitchingCard.select().join(Player).where(
(PitchingCard.player << legal_players) & (PitchingCard.relief_rating >= 3) &
(PitchingCard.starter_rating < 4)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << legal_players) & (PitchingCard.relief_rating >= 2) &
(PitchingCard.starter_rating < 4)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << backup_players) & (PitchingCard.relief_rating >= 2) &
(PitchingCard.starter_rating < 4))
]:
all_relievers = sort_pitchers(query)
if all_relievers is not None:
this_player_id = all_relievers.iloc[0].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
elif need == 'middle':
for query in [PitchingCard.select().join(Player).where(
(PitchingCard.player << legal_players) & (PitchingCard.starter_rating == 1)),
PitchingCard.select().join(Player).where(
(PitchingCard.player << backup_players) & (PitchingCard.starter_rating < 4))
]:
all_relievers = sort_pitchers(query)
if all_relievers is not None and len(all_relievers.index) >= 3:
this_player_id = all_relievers.iloc[2].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
logging.info(f'Falling to last chance pitcher')
all_relievers = sort_pitchers(
PitchingCard.select().join(Player).where(
(PitchingCard.player << backup_players) | (PitchingCard.player << legal_players)
)
)
if all_relievers is not None:
this_player_id = all_relievers.iloc[len(all_relievers.index) - 1].player
this_player = model_to_dict(Player.get_by_id(this_player_id), recurse=False)
db.close()
return this_player
@router.get('/{team_id}/buy/players')
async def team_buy_players(team_id: int, ids: str, ts: str):
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
if ts != this_team.team_hash():
logging.warning(f'Bad Team Secret: {ts} ({this_team.team_hash()})')
db.close()
raise HTTPException(
status_code=401,
detail=f'You are not authorized to buy {this_team.abbrev} cards. This event has been logged.'
)
last_card = Card.select(Card.id).order_by(-Card.id).limit(1)
lc_id = last_card[0].id
all_ids = ids.split(',')
conf_message = ''
total_cost = 0
for player_id in all_ids:
if player_id != '':
try:
this_player = Player.get_by_id(player_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No player found with id {player_id} /// '
f'{conf_message} purchased')
# check wallet balance
if this_team.wallet < this_player.cost:
logging.info(f'{this_player} was not purchased. {this_team.lname} only has {this_team.wallet}₼, but '
f'{this_player} costs {this_player.cost}₼.')
db.close()
raise HTTPException(
200,
detail=f'{this_player} was not purchased. {this_team.lname} only has {this_team.wallet}₼, but '
f'{this_player} costs {this_player.cost}₼. /// {conf_message} purchased'
)
# Create player card and update cost
buy_price = this_player.cost
total_cost += buy_price
this_card = Card(
player_id=this_player.player_id,
team_id=this_team.id,
value=buy_price
)
Paperdex.get_or_create(team_id=team_id, player_id=this_player.player_id)
this_card.save()
this_player.change_on_buy()
# Deduct card cost from team
logging.info(f'{this_team.abbrev} starting wallet: {this_team.wallet}')
this_team.wallet -= buy_price
this_team.save()
logging.info(f'{this_team.abbrev} ending wallet: {this_team.wallet}')
# Post a notification
if this_player.rarity.value >= 2:
new_notif = Notification(
created=int_timestamp(datetime.now()),
title=f'Price Change',
desc='Modified by buying and selling',
field_name=f'{this_player.description} '
f'{this_player.p_name if this_player.p_name not in this_player.description else ""}',
message=f'From {buy_price}₼ 📈 to **{this_player.cost}**₼',
about=f'Player-{this_player.player_id}'
)
new_notif.save()
conf_message += f'{buy_price}₼ for {this_player.rarity.name} {this_player.p_name} ' \
f'({this_player.cardset.name}), '
# sheets.post_new_cards(SHEETS_AUTH, lc_id)
raise HTTPException(status_code=200, detail=f'{conf_message} purchased. /// Total Cost: {total_cost}₼ /// '
f'Final Wallet: {this_team.wallet}')
@router.get('/{team_id}/buy/pack/{packtype_id}')
async def team_buy_packs(team_id: int, packtype_id: int, ts: str, quantity: Optional[int] = 1):
try:
this_packtype = PackType.get_by_id(packtype_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No pack type found with id {packtype_id}')
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
if ts != this_team.team_hash():
logging.warning(f'Bad Team Secret: {ts} ({this_team.team_hash()})')
db.close()
logging.warning(f'team: {this_team} / pack_type: {this_packtype} / secret: {ts} / '
f'actual: {this_team.team_hash()}')
raise HTTPException(
status_code=401,
detail=f'You are not authorized to buy {this_team.abbrev} packs. This event has been logged.'
)
# check wallet balance
total_cost = this_packtype.cost * quantity
if this_team.wallet < total_cost:
db.close()
raise HTTPException(
200,
detail=f'{this_packtype} was not purchased. {this_team.lname} only has {this_team.wallet} bucks, but '
f'{this_packtype} costs {this_packtype.cost}.'
)
all_packs = []
for i in range(quantity):
all_packs.append(Pack(team_id=this_team.id, pack_type_id=this_packtype.id))
# Deduct card cost from team
logging.info(f'{this_team.abbrev} starting wallet: {this_team.wallet}')
this_team.wallet -= total_cost
this_team.save()
logging.info(f'{this_team.abbrev} ending wallet: {this_team.wallet}')
with db.atomic():
Pack.bulk_create(all_packs, batch_size=15)
db.close()
raise HTTPException(
status_code=200,
detail=f'Quantity {quantity} {this_packtype.name} pack{"s" if quantity > 1 else ""} have been purchased by '
f'{this_team.lname} for {total_cost} bucks. You may close this window.'
)
@router.get('/{team_id}/sell/cards')
async def team_sell_cards(team_id: int, ids: str, ts: str):
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
if ts != this_team.team_hash():
logging.warning(f'Bad Team Secret: {ts} ({this_team.team_hash()})')
db.close()
raise HTTPException(
status_code=401,
detail=f'You are not authorized to sell {this_team.abbrev} cards. This event has been logged.'
)
all_ids = ids.split(',')
del_ids = []
conf_message = ''
total_cost = 0
for card_id in all_ids:
if card_id != '':
try:
this_card = Card.get_by_id(card_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No card found with id {card_id}')
del_ids.append(card_id)
this_player = this_card.player
if this_card.team != this_team:
raise HTTPException(status_code=401,
detail=f'Card id {card_id} ({this_player.p_name}) belongs to '
f'{this_card.team.abbrev} and cannot be sold. /// {conf_message} sold')
orig_price = this_player.cost
sell_price = round(this_player.cost * .5)
total_cost += sell_price
# credit selling team's wallet
if this_team.wallet is None:
this_team.wallet = sell_price
else:
this_team.wallet += sell_price
this_team.save()
# decrease price of player
this_player.change_on_sell()
this_card.delete_instance()
# post a notification
if this_player.rarity.value >= 2:
new_notif = Notification(
created=int_timestamp(datetime.now()),
title=f'Price Change',
desc='Modified by buying and selling',
field_name=f'{this_player.description} '
f'{this_player.p_name if this_player.p_name not in this_player.description else ""}',
message=f'From {orig_price}₼ 📉 to **{this_player.cost}**₼',
about=f'Player-{this_player.id}'
)
new_notif.save()
conf_message += f'{sell_price}₼ for {this_player.rarity.name} {this_player.p_name} ' \
f'({this_player.cardset.name}), '
# sheets.post_deletion(SHEETS_AUTH, del_ids)
raise HTTPException(status_code=200, detail=f'{conf_message} sold. /// Total Earned: {total_cost}₼ /// '
f'Final Wallet: {this_team.wallet}')
@router.get('/{team_id}/cards')
async def get_team_cards(team_id, csv: Optional[bool] = True):
"""
CSV output specifically targeting team roster sheet
Parameters
----------
team_id
csv
"""
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
if not csv:
db.close()
raise HTTPException(
status_code=400,
detail='The /teams/{team_id}/cards endpoint only supports csv output.'
)
all_cards = (Card
.select()
.join(Player)
.join(Rarity)
.where(Card.team == this_team)
.order_by(-Card.player.rarity.value, Card.player.p_name)
)
if all_cards.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No cards found')
card_vals = [model_to_dict(x) for x in all_cards]
db.close()
for x in card_vals:
x.update(x['player'])
x['player_id'] = x['player']['player_id']
x['player_name'] = x['player']['p_name']
x['cardset_id'] = x['player']['cardset']['id']
x['cardset_name'] = x['player']['cardset']['name']
x['rarity'] = x['player']['rarity']['name']
x['card_id'] = x['id']
card_df = pd.DataFrame(card_vals)
output = card_df[[
'cardset_name', 'player_name', 'rarity', 'image', 'image2', 'pos_1', 'pos_2', 'pos_3', 'pos_4', 'pos_5',
'pos_6', 'pos_7', 'pos_8', 'cost', 'mlbclub', 'franchise', 'fangr_id', 'bbref_id', 'player_id', 'card_id']]
return Response(content=pd.DataFrame(output).to_csv(index=False), media_type='text/csv')
@router.post('')
async def post_team(team: TeamModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post teams. This event has been logged.'
)
dupe_team = Team.get_or_none(Team.season == team.season, Team.abbrev == team.abbrev)
if dupe_team:
db.close()
raise HTTPException(status_code=400, detail=f'There is already a season {team.season} team using {team.abbrev}')
this_team = Team(
abbrev=team.abbrev,
sname=team.sname,
lname=team.lname,
gmid=team.gmid,
gmname=team.gmname,
wallet=team.wallet,
gsheet=team.gsheet,
team_value=team.team_value,
collection_value=team.collection_value,
logo=team.logo,
color=team.color,
ranking=team.ranking,
season=team.season,
career=team.ps_shiny,
has_guide=team.has_guide,
is_ai=team.is_ai
)
saved = this_team.save()
if saved == 1:
return_team = model_to_dict(this_team)
db.close()
return return_team
else:
raise HTTPException(status_code=418, detail='Well slap my ass and call me a teapot; I could not save that team')
@router.post('/new-season/{new_season}')
async def team_season_update(new_season: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to post teams. This event has been logged.'
)
r_query = Team.update(ranking=1000, season=new_season, wallet=Team.wallet + 250).execute()
current = Current.latest()
current.season = new_season
current.save()
db.close()
return {'detail': f'Team rankings, season, and wallet updated for season {new_season}'}
@router.post('/{team_id}/money/{delta}')
async def team_update_money(team_id: int, delta: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to adjust wallets. This event has been logged.'
)
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
this_team.wallet += delta
if this_team.save() == 1:
return_team = model_to_dict(this_team)
db.close()
return return_team
else:
raise HTTPException(status_code=418, detail='Well slap my ass and call me a teapot; I could not save that team')
@router.patch('/{team_id}')
async def patch_team(
team_id, sname: Optional[str] = None, lname: Optional[str] = None, gmid: Optional[int] = None,
gmname: Optional[str] = None, gsheet: Optional[str] = None, team_value: Optional[int] = None,
collection_value: Optional[int] = None, logo: Optional[str] = None, color: Optional[str] = None,
season: Optional[int] = None, ps_shiny: Optional[int] = None, wallet_delta: Optional[int] = None,
has_guide: Optional[bool] = None, is_ai: Optional[bool] = None, ranking: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete teams. This event has been logged.'
)
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
if sname is not None:
this_team.sname = sname
if lname is not None:
this_team.lname = lname
if gmid is not None:
this_team.gmid = gmid
if gmname is not None:
this_team.gmname = gmname
if gsheet is not None:
this_team.gsheet = gsheet
if team_value is not None:
this_team.team_value = team_value
if collection_value is not None:
this_team.collection_value = collection_value
if logo is not None:
this_team.logo = logo
if color is not None:
this_team.color = color
if season is not None:
this_team.season = season
if ps_shiny is not None:
this_team.career = ps_shiny
if ranking is not None:
this_team.ranking = ranking
if wallet_delta is not None:
this_team.wallet += wallet_delta
if has_guide is not None:
if has_guide:
this_team.has_guide = 1
else:
this_team.has_guide = 0
if is_ai is not None:
if is_ai:
this_team.is_ai = 1
else:
this_team.is_ai = 0
if this_team.save() == 1:
return_team = model_to_dict(this_team)
db.close()
return return_team
else:
raise HTTPException(status_code=418, detail='Well slap my ass and call me a teapot; I could not save that team')
@router.delete('/{team_id}')
async def delete_team(team_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'Bad Token: {token}')
db.close()
raise HTTPException(
status_code=401,
detail='You are not authorized to delete teams. This event has been logged.'
)
try:
this_team = Team.get_by_id(team_id)
except Exception:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {team_id}')
count = this_team.delete_instance()
db.close()
if count == 1:
raise HTTPException(status_code=200, detail=f'Team {team_id} has been deleted')
else:
raise HTTPException(status_code=500, detail=f'Team {team_id} was not deleted')

View File

@ -1,9 +1,13 @@
import math
from datetime import datetime
from typing import List
import logging
import os
from pandas import DataFrame
from peewee import *
from peewee import ModelSelect
from playhouse.shortcuts import model_to_dict
db = SqliteDatabase(
'storage/pd_master.db',
@ -23,6 +27,62 @@ logging.basicConfig(
)
def model_csv_headers(this_obj, exclude=None) -> List:
data = model_to_dict(this_obj, recurse=False, exclude=exclude)
return [x for x in data.keys()]
def model_to_csv(this_obj, exclude=None) -> List:
data = model_to_dict(this_obj, recurse=False, exclude=exclude)
return [x for x in data.values()]
def query_to_csv(all_items: ModelSelect, exclude=None):
if all_items.count() == 0:
data_list = [['No data found']]
else:
data_list = [model_csv_headers(all_items[0], exclude=exclude)]
for x in all_items:
data_list.append(model_to_csv(x, exclude=exclude))
return DataFrame(data_list).to_csv(header=False, index=False)
def complex_data_to_csv(complex_data: List):
if len(complex_data) == 0:
data_list = [['No data found']]
else:
data_list = [[x for x in complex_data[0].keys()]]
for line in complex_data:
logging.debug(f'line: {line}')
this_row = []
for key in line:
logging.debug(f'key: {key}')
if line[key] is None:
this_row.append('')
elif isinstance(line[key], dict):
if 'name' in line[key]:
this_row.append(line[key]['name'])
elif 'abbrev' in line[key]:
this_row.append(line[key]['abbrev'])
else:
this_row.append(line[key]['id'])
elif isinstance(line[key], int) and line[key] > 100000000:
this_row.append(f"'{line[key]}")
elif isinstance(line[key], str) and ',' in line[key]:
this_row.append(line[key].replace(",", "-_-"))
else:
this_row.append(line[key])
data_list.append(this_row)
return DataFrame(data_list).to_csv(header=False, index=False)
class BaseModel(Model):
class Meta:
database = db
@ -84,6 +144,19 @@ class Cardset(BaseModel):
db.create_tables([Cardset])
class MlbPlayer(BaseModel):
first_name = CharField()
last_name = CharField()
key_fangraphs = IntegerField(null=True)
key_bbref = CharField(null=True)
key_retro = CharField(null=True)
key_mlbam = IntegerField(null=True)
offense_col = IntegerField(default=1)
db.create_tables([MlbPlayer])
class Player(BaseModel):
player_id = IntegerField(primary_key=True)
p_name = CharField()
@ -110,6 +183,7 @@ class Player(BaseModel):
fangr_id = CharField(null=True)
description = CharField()
quantity = IntegerField(default=999)
mlbplayer = ForeignKeyField(MlbPlayer, null=True)
def __str__(self):
return f'{self.cardset} {self.p_name} ({self.rarity.name})'
@ -321,11 +395,35 @@ class Roster(BaseModel):
# this_roster.card26]
class Result(BaseModel):
away_team = ForeignKeyField(Team)
home_team = ForeignKeyField(Team)
away_score = IntegerField()
home_score = IntegerField()
away_team_value = IntegerField(null=True)
home_team_value = IntegerField(null=True)
away_team_ranking = IntegerField(null=True)
home_team_ranking = IntegerField(null=True)
scorecard = CharField()
week = IntegerField()
season = IntegerField()
ranked = BooleanField()
short_game = BooleanField()
game_type = CharField(null=True)
@staticmethod
def select_season(season=None):
if not season:
season = Current.get().season
return Result.select().where(Result.season == season)
class BattingStat(BaseModel):
card = ForeignKeyField(Card)
team = ForeignKeyField(Team)
roster_num = IntegerField()
vs_team = ForeignKeyField(Team)
result = ForeignKeyField(Result, null=True)
pos = CharField()
pa = IntegerField()
ab = IntegerField()
@ -364,6 +462,7 @@ class PitchingStat(BaseModel):
team = ForeignKeyField(Team)
roster_num = IntegerField()
vs_team = ForeignKeyField(Team)
result = ForeignKeyField(Result, null=True)
ip = FloatField()
hit = IntegerField()
run = IntegerField()
@ -388,29 +487,6 @@ class PitchingStat(BaseModel):
game_id = IntegerField()
class Result(BaseModel):
away_team = ForeignKeyField(Team)
home_team = ForeignKeyField(Team)
away_score = IntegerField()
home_score = IntegerField()
away_team_value = IntegerField(null=True)
home_team_value = IntegerField(null=True)
away_team_ranking = IntegerField(null=True)
home_team_ranking = IntegerField(null=True)
scorecard = CharField()
week = IntegerField()
season = IntegerField()
ranked = BooleanField()
short_game = BooleanField()
game_type = CharField(null=True)
@staticmethod
def select_season(season=None):
if not season:
season = Current.get().season
return Result.select().where(Result.season == season)
class Award(BaseModel):
name = CharField()
season = IntegerField()
@ -484,6 +560,7 @@ db.create_tables([
class BattingCard(BaseModel):
player = ForeignKeyField(Player)
variant = IntegerField()
steal_low = IntegerField()
steal_high = IntegerField()
steal_auto = BooleanField()
@ -495,9 +572,16 @@ class BattingCard(BaseModel):
hand = CharField(default='R')
bc_index = ModelIndex(BattingCard, (BattingCard.player, BattingCard.variant), unique=True)
BattingCard.add_index(bc_index)
class BattingCardRatings(BaseModel):
battingcard = ForeignKeyField(BattingCard)
vs_hand = FloatField()
vs_hand = CharField(default='R')
pull_rate = FloatField()
center_rate = FloatField()
slap_rate = FloatField()
homerun = FloatField()
bp_homerun = FloatField()
triple = FloatField()
@ -525,20 +609,33 @@ class BattingCardRatings(BaseModel):
slg = FloatField(null=True)
bcr_index = ModelIndex(
BattingCardRatings, (BattingCardRatings.battingcard, BattingCardRatings.vs_hand), unique=True
)
BattingCardRatings.add_index(bcr_index)
class PitchingCard(BaseModel):
player = ForeignKeyField(Player)
variant = IntegerField()
balk = IntegerField()
wild_pitch = IntegerField(null=True)
hold = CharField()
wild_pitch = IntegerField()
hold = IntegerField()
starter_rating = IntegerField()
relief_rating = IntegerField()
closer_rating = IntegerField(null=True)
batting = CharField(null=True)
offense_col = IntegerField()
hand = CharField(default='R')
pc_index = ModelIndex(PitchingCard, (PitchingCard.player, PitchingCard.variant), unique=True)
PitchingCard.add_index(pc_index)
class PitchingCardRatings(BaseModel):
pitchingcard = ForeignKeyField(PitchingCard)
vs_hand = CharField()
vs_hand = CharField(default='R')
homerun = FloatField()
bp_homerun = FloatField()
triple = FloatField()
@ -552,8 +649,9 @@ class PitchingCardRatings(BaseModel):
hbp = FloatField()
walk = FloatField()
strikeout = FloatField()
fo_slap = FloatField()
fo_center = FloatField()
flyout_lf_b = FloatField()
flyout_cf_b = FloatField()
flyout_rf_b = FloatField()
groundout_a = FloatField()
groundout_b = FloatField()
xcheck_p = FloatField()
@ -570,10 +668,15 @@ class PitchingCardRatings(BaseModel):
slg = FloatField(null=True)
pcr_index = ModelIndex(
PitchingCardRatings, (PitchingCardRatings.pitchingcard, PitchingCardRatings.vs_hand), unique=True
)
PitchingCardRatings.add_index(pcr_index)
class CardPosition(BaseModel):
player = ForeignKeyField(Player)
batting = ForeignKeyField(BattingCard, null=True)
pitching = ForeignKeyField(PitchingCard, null=True)
variant = IntegerField()
position = CharField()
innings = IntegerField()
range = IntegerField()
@ -583,182 +686,188 @@ class CardPosition(BaseModel):
overthrow = IntegerField(null=True)
pos_index = ModelIndex(
CardPosition, (CardPosition.player, CardPosition.variant, CardPosition.position), unique=True
)
CardPosition.add_index(pos_index)
db.create_tables([BattingCard, BattingCardRatings, PitchingCard, PitchingCardRatings, CardPosition])
db.close()
scout_db = SqliteDatabase(
'storage/card_creation.db',
pragmas={
'journal_mode': 'wal',
'cache_size': -1 * 64000,
'synchronous': 0
}
)
class BaseModelScout(Model):
class Meta:
database = scout_db
class ScoutCardset(BaseModelScout):
set_title = CharField()
set_subtitle = CharField(null=True)
class ScoutPlayer(BaseModelScout):
sba_id = IntegerField(primary_key=True)
name = CharField()
fg_id = IntegerField()
br_id = CharField()
offense_col = IntegerField()
hand = CharField(default='R')
scout_db.create_tables([ScoutCardset, ScoutPlayer])
class BatterRatings(BaseModelScout):
id = CharField(unique=True, primary_key=True)
player = ForeignKeyField(ScoutPlayer)
cardset = ForeignKeyField(ScoutCardset)
vs_hand = FloatField()
is_prep = BooleanField()
homerun = FloatField()
bp_homerun = FloatField()
triple = FloatField()
double_three = FloatField()
double_two = FloatField()
double_pull = FloatField()
single_two = FloatField()
single_one = FloatField()
single_center = FloatField()
bp_single = FloatField()
hbp = FloatField()
walk = FloatField()
strikeout = FloatField()
lineout = FloatField()
popout = FloatField()
flyout_a = FloatField()
flyout_bq = FloatField()
flyout_lf_b = FloatField()
flyout_rf_b = FloatField()
groundout_a = FloatField()
groundout_b = FloatField()
groundout_c = FloatField()
avg = FloatField(null=True)
obp = FloatField(null=True)
slg = FloatField(null=True)
class PitcherRatings(BaseModelScout):
id = CharField(unique=True, primary_key=True)
player = ForeignKeyField(ScoutPlayer)
cardset = ForeignKeyField(ScoutCardset)
vs_hand = CharField()
is_prep = BooleanField()
homerun = FloatField()
bp_homerun = FloatField()
triple = FloatField()
double_three = FloatField()
double_two = FloatField()
double_cf = FloatField()
single_two = FloatField()
single_one = FloatField()
single_center = FloatField()
bp_single = FloatField()
hbp = FloatField()
walk = FloatField()
strikeout = FloatField()
fo_slap = FloatField()
fo_center = FloatField()
groundout_a = FloatField()
groundout_b = FloatField()
xcheck_p = FloatField()
xcheck_c = FloatField()
xcheck_1b = FloatField()
xcheck_2b = FloatField()
xcheck_3b = FloatField()
xcheck_ss = FloatField()
xcheck_lf = FloatField()
xcheck_cf = FloatField()
xcheck_rf = FloatField()
avg = FloatField(null=True)
obp = FloatField(null=True)
slg = FloatField(null=True)
# scout_db.create_tables([BatterRatings, PitcherRatings])
class CardColumns(BaseModelScout):
id = CharField(unique=True, primary_key=True)
player = ForeignKeyField(ScoutPlayer)
hand = CharField()
b_ratings = ForeignKeyField(BatterRatings, null=True)
p_ratings = ForeignKeyField(PitcherRatings, null=True)
one_dice = CharField()
one_results = CharField()
one_splits = CharField()
two_dice = CharField()
two_results = CharField()
two_splits = CharField()
three_dice = CharField()
three_results = CharField()
three_splits = CharField()
class Position(BaseModelScout):
player = ForeignKeyField(ScoutPlayer)
cardset = ForeignKeyField(ScoutCardset)
position = CharField()
innings = IntegerField()
range = IntegerField()
error = IntegerField()
arm = CharField(null=True)
pb = IntegerField(null=True)
overthrow = IntegerField(null=True)
class BatterData(BaseModelScout):
player = ForeignKeyField(ScoutPlayer)
cardset = ForeignKeyField(ScoutCardset)
stealing = CharField()
st_low = IntegerField()
st_high = IntegerField()
st_auto = BooleanField()
st_jump = FloatField()
bunting = CharField(null=True)
hit_and_run = CharField(null=True)
running = CharField()
class PitcherData(BaseModelScout):
player = ForeignKeyField(ScoutPlayer)
cardset = ForeignKeyField(ScoutCardset)
balk = IntegerField(null=True)
wild_pitch = IntegerField(null=True)
hold = CharField()
starter_rating = IntegerField()
relief_rating = IntegerField()
closer_rating = IntegerField(null=True)
batting = CharField(null=True)
scout_db.create_tables([CardColumns, Position, BatterData, PitcherData])
class CardOutput(BaseModelScout):
name = CharField()
hand = CharField()
positions = CharField()
stealing = CharField()
bunting = CharField()
hitandrun = CharField()
running = CharField()
scout_db.close()
# scout_db = SqliteDatabase(
# 'storage/card_creation.db',
# pragmas={
# 'journal_mode': 'wal',
# 'cache_size': -1 * 64000,
# 'synchronous': 0
# }
# )
#
#
# class BaseModelScout(Model):
# class Meta:
# database = scout_db
#
#
# class ScoutCardset(BaseModelScout):
# set_title = CharField()
# set_subtitle = CharField(null=True)
#
#
# class ScoutPlayer(BaseModelScout):
# sba_id = IntegerField(primary_key=True)
# name = CharField()
# fg_id = IntegerField()
# br_id = CharField()
# offense_col = IntegerField()
# hand = CharField(default='R')
#
#
# scout_db.create_tables([ScoutCardset, ScoutPlayer])
#
#
# class BatterRatings(BaseModelScout):
# id = CharField(unique=True, primary_key=True)
# player = ForeignKeyField(ScoutPlayer)
# cardset = ForeignKeyField(ScoutCardset)
# vs_hand = FloatField()
# is_prep = BooleanField()
# homerun = FloatField()
# bp_homerun = FloatField()
# triple = FloatField()
# double_three = FloatField()
# double_two = FloatField()
# double_pull = FloatField()
# single_two = FloatField()
# single_one = FloatField()
# single_center = FloatField()
# bp_single = FloatField()
# hbp = FloatField()
# walk = FloatField()
# strikeout = FloatField()
# lineout = FloatField()
# popout = FloatField()
# flyout_a = FloatField()
# flyout_bq = FloatField()
# flyout_lf_b = FloatField()
# flyout_rf_b = FloatField()
# groundout_a = FloatField()
# groundout_b = FloatField()
# groundout_c = FloatField()
# avg = FloatField(null=True)
# obp = FloatField(null=True)
# slg = FloatField(null=True)
#
#
# class PitcherRatings(BaseModelScout):
# id = CharField(unique=True, primary_key=True)
# player = ForeignKeyField(ScoutPlayer)
# cardset = ForeignKeyField(ScoutCardset)
# vs_hand = CharField()
# is_prep = BooleanField()
# homerun = FloatField()
# bp_homerun = FloatField()
# triple = FloatField()
# double_three = FloatField()
# double_two = FloatField()
# double_cf = FloatField()
# single_two = FloatField()
# single_one = FloatField()
# single_center = FloatField()
# bp_single = FloatField()
# hbp = FloatField()
# walk = FloatField()
# strikeout = FloatField()
# fo_slap = FloatField()
# fo_center = FloatField()
# groundout_a = FloatField()
# groundout_b = FloatField()
# xcheck_p = FloatField()
# xcheck_c = FloatField()
# xcheck_1b = FloatField()
# xcheck_2b = FloatField()
# xcheck_3b = FloatField()
# xcheck_ss = FloatField()
# xcheck_lf = FloatField()
# xcheck_cf = FloatField()
# xcheck_rf = FloatField()
# avg = FloatField(null=True)
# obp = FloatField(null=True)
# slg = FloatField(null=True)
#
#
# # scout_db.create_tables([BatterRatings, PitcherRatings])
#
#
# class CardColumns(BaseModelScout):
# id = CharField(unique=True, primary_key=True)
# player = ForeignKeyField(ScoutPlayer)
# hand = CharField()
# b_ratings = ForeignKeyField(BatterRatings, null=True)
# p_ratings = ForeignKeyField(PitcherRatings, null=True)
# one_dice = CharField()
# one_results = CharField()
# one_splits = CharField()
# two_dice = CharField()
# two_results = CharField()
# two_splits = CharField()
# three_dice = CharField()
# three_results = CharField()
# three_splits = CharField()
#
#
# class Position(BaseModelScout):
# player = ForeignKeyField(ScoutPlayer)
# cardset = ForeignKeyField(ScoutCardset)
# position = CharField()
# innings = IntegerField()
# range = IntegerField()
# error = IntegerField()
# arm = CharField(null=True)
# pb = IntegerField(null=True)
# overthrow = IntegerField(null=True)
#
#
# class BatterData(BaseModelScout):
# player = ForeignKeyField(ScoutPlayer)
# cardset = ForeignKeyField(ScoutCardset)
# stealing = CharField()
# st_low = IntegerField()
# st_high = IntegerField()
# st_auto = BooleanField()
# st_jump = FloatField()
# bunting = CharField(null=True)
# hit_and_run = CharField(null=True)
# running = CharField()
#
#
# class PitcherData(BaseModelScout):
# player = ForeignKeyField(ScoutPlayer)
# cardset = ForeignKeyField(ScoutCardset)
# balk = IntegerField(null=True)
# wild_pitch = IntegerField(null=True)
# hold = CharField()
# starter_rating = IntegerField()
# relief_rating = IntegerField()
# closer_rating = IntegerField(null=True)
# batting = CharField(null=True)
#
#
# scout_db.create_tables([CardColumns, Position, BatterData, PitcherData])
#
#
# class CardOutput(BaseModelScout):
# name = CharField()
# hand = CharField()
# positions = CharField()
# stealing = CharField()
# bunting = CharField()
# hitandrun = CharField()
# running = CharField()
#
#
# scout_db.close()

View File

@ -11,23 +11,28 @@ migrator = SqliteMigrator(db_engine.db)
# pitcher_injury = IntegerField(null=True)
# pos_1 = CharField(default='None')
# offense_col = IntegerField(null=True)
# pos_2 = CharField(null=True)
# last_game = CharField(null=True)
# game_type = CharField(null=True)
# pack_type = ForeignKeyField(PackType, default=1, to_field='id', field_type=int)
mlb_player = ForeignKeyField(db_engine.MlbPlayer, field=db_engine.MlbPlayer.id, null=True)
result = ForeignKeyField(db_engine.Result, field=db_engine.Result.id, null=True)
# active_theme = ForeignKeyField(PackTheme, to_field='id', field_type=int, null=True)
# active_theme = ForeignKeyField(db_engine.PackTheme, field=db_engine.PackTheme.id, null=True) # for careers
# game_type = CharField(null=True)
# pack_team = ForeignKeyField(db_engine.Team, field=db_engine.Team.id, null=True)
pack_cardset = ForeignKeyField(db_engine.Cardset, field=db_engine.Cardset.id, null=True)
# pack_cardset = ForeignKeyField(db_engine.Cardset, field=db_engine.Cardset.id, null=True)
pull_rate = FloatField(default=0.333)
migrate(
# migrator.add_column('current', 'active_theme_id', active_theme),
# migrator.add_column('pack', 'pack_team_id', pack_team),
migrator.add_column('pack', 'pack_cardset_id', pack_cardset),
# migrator.add_column('player', 'mlbplayer_id', mlb_player),
migrator.add_column('battingstat', 'result_id', result),
migrator.add_column('pitchingstat', 'result_id', result),
# migrator.add_column('battingcardratings', 'pull_rate', pull_rate),
# migrator.rename_column('cardset', 'available', 'for_purchase')
# migrator.add_column('player', 'pos_1', pos_1),
# migrator.add_column('player', 'offense_col', offense_col),
# migrator.add_column('comment_tbl', 'comment', comment_field),
# migrator.rename_column('story', 'pub_date', 'publish_date'),
# migrator.drop_column('story', 'some_old_field'),

View File

@ -4,3 +4,8 @@ peewee
python-multipart
pandas
pygsheets
pybaseball
python-multipart
requests
html2image
jinja2

225
venv/share/man/man1/ttx.1 Normal file
View File

@ -0,0 +1,225 @@
.Dd May 18, 2004
.\" ttx is not specific to any OS, but contrary to what groff_mdoc(7)
.\" seems to imply, entirely omitting the .Os macro causes 'BSD' to
.\" be used, so I give a zero-width space as its argument.
.Os \&
.\" The "FontTools Manual" argument apparently has no effect in
.\" groff 1.18.1. I think it is a bug in the -mdoc groff package.
.Dt TTX 1 "FontTools Manual"
.Sh NAME
.Nm ttx
.Nd tool for manipulating TrueType and OpenType fonts
.Sh SYNOPSIS
.Nm
.Bk
.Op Ar option ...
.Ek
.Bk
.Ar file ...
.Ek
.Sh DESCRIPTION
.Nm
is a tool for manipulating TrueType and OpenType fonts. It can convert
TrueType and OpenType fonts to and from an
.Tn XML Ns -based format called
.Tn TTX .
.Tn TTX
files have a
.Ql .ttx
extension.
.Pp
For each
.Ar file
argument it is given,
.Nm
detects whether it is a
.Ql .ttf ,
.Ql .otf
or
.Ql .ttx
file and acts accordingly: if it is a
.Ql .ttf
or
.Ql .otf
file, it generates a
.Ql .ttx
file; if it is a
.Ql .ttx
file, it generates a
.Ql .ttf
or
.Ql .otf
file.
.Pp
By default, every output file is created in the same directory as the
corresponding input file and with the same name except for the
extension, which is substituted appropriately.
.Nm
never overwrites existing files; if necessary, it appends a suffix to
the output file name before the extension, as in
.Pa Arial#1.ttf .
.Ss "General options"
.Bl -tag -width ".Fl t Ar table"
.It Fl h
Display usage information.
.It Fl d Ar dir
Write the output files to directory
.Ar dir
instead of writing every output file to the same directory as the
corresponding input file.
.It Fl o Ar file
Write the output to
.Ar file
instead of writing it to the same directory as the
corresponding input file.
.It Fl v
Be verbose. Write more messages to the standard output describing what
is being done.
.It Fl a
Allow virtual glyphs ID's on compile or decompile.
.El
.Ss "Dump options"
The following options control the process of dumping font files
(TrueType or OpenType) to
.Tn TTX
files.
.Bl -tag -width ".Fl t Ar table"
.It Fl l
List table information. Instead of dumping the font to a
.Tn TTX
file, display minimal information about each table.
.It Fl t Ar table
Dump table
.Ar table .
This option may be given multiple times to dump several tables at
once. When not specified, all tables are dumped.
.It Fl x Ar table
Exclude table
.Ar table
from the list of tables to dump. This option may be given multiple
times to exclude several tables from the dump. The
.Fl t
and
.Fl x
options are mutually exclusive.
.It Fl s
Split tables. Dump each table to a separate
.Tn TTX
file and write (under the name that would have been used for the output
file if the
.Fl s
option had not been given) one small
.Tn TTX
file containing references to the individual table dump files. This
file can be used as input to
.Nm
as long as the referenced files can be found in the same directory.
.It Fl i
.\" XXX: I suppose OpenType programs (exist and) are also affected.
Don't disassemble TrueType instructions. When this option is specified,
all TrueType programs (glyph programs, the font program and the
pre-program) are written to the
.Tn TTX
file as hexadecimal data instead of
assembly. This saves some time and results in smaller
.Tn TTX
files.
.It Fl y Ar n
When decompiling a TrueType Collection (TTC) file,
decompile font number
.Ar n ,
starting from 0.
.El
.Ss "Compilation options"
The following options control the process of compiling
.Tn TTX
files into font files (TrueType or OpenType):
.Bl -tag -width ".Fl t Ar table"
.It Fl m Ar fontfile
Merge the input
.Tn TTX
file
.Ar file
with
.Ar fontfile .
No more than one
.Ar file
argument can be specified when this option is used.
.It Fl b
Don't recalculate glyph bounding boxes. Use the values in the
.Tn TTX
file as is.
.El
.Sh "THE TTX FILE FORMAT"
You can find some information about the
.Tn TTX
file format in
.Pa documentation.html .
In particular, you will find in that file the list of tables understood by
.Nm
and the relations between TrueType GlyphIDs and the glyph names used in
.Tn TTX
files.
.Sh EXAMPLES
In the following examples, all files are read from and written to the
current directory. Additionally, the name given for the output file
assumes in every case that it did not exist before
.Nm
was invoked.
.Pp
Dump the TrueType font contained in
.Pa FreeSans.ttf
to
.Pa FreeSans.ttx :
.Pp
.Dl ttx FreeSans.ttf
.Pp
Compile
.Pa MyFont.ttx
into a TrueType or OpenType font file:
.Pp
.Dl ttx MyFont.ttx
.Pp
List the tables in
.Pa FreeSans.ttf
along with some information:
.Pp
.Dl ttx -l FreeSans.ttf
.Pp
Dump the
.Sq cmap
table from
.Pa FreeSans.ttf
to
.Pa FreeSans.ttx :
.Pp
.Dl ttx -t cmap FreeSans.ttf
.Sh NOTES
On MS\-Windows and MacOS,
.Nm
is available as a graphical application to which files can be dropped.
.Sh SEE ALSO
.Pa documentation.html
.Pp
.Xr fontforge 1 ,
.Xr ftinfo 1 ,
.Xr gfontview 1 ,
.Xr xmbdfed 1 ,
.Xr Font::TTF 3pm
.Sh AUTHORS
.Nm
was written by
.An -nosplit
.An "Just van Rossum" Aq just@letterror.com .
.Pp
This manual page was written by
.An "Florent Rougon" Aq f.rougon@free.fr
for the Debian GNU/Linux system based on the existing FontTools
documentation. It may be freely used, modified and distributed without
restrictions.
.\" For Emacs:
.\" Local Variables:
.\" fill-column: 72
.\" sentence-end: "[.?!][]\"')}]*\\($\\| $\\| \\| \\)[ \n]*"
.\" sentence-end-double-space: t
.\" End: