Merge pull request #2 from calcorum/api-rebuild

Api rebuild
This commit is contained in:
Cal Corum 2023-07-24 23:06:53 -05:00 committed by GitHub
commit 6faec225d3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
24 changed files with 4693 additions and 5 deletions

View File

@ -1,8 +1,8 @@
FROM tiangolo/uvicorn-gunicorn-fastapi:python3.8
FROM tiangolo/uvicorn-gunicorn-fastapi:latest
WORKDIR /usr/src/app
COPY requirements.txt ./
RUN pip install --no-cache-dir -r requirements.txt
COPY . .
COPY ./app /app/app

0
app/__init__.py Normal file
View File

1940
app/db_engine.py Normal file

File diff suppressed because it is too large Load Diff

34
app/dependencies.py Normal file
View File

@ -0,0 +1,34 @@
import datetime
import logging
import os
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']
)
# date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
# log_level = logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
# logging.basicConfig(
# filename=f'logs/database/{date}.log',
# format='%(asctime)s - sba-database - %(levelname)s - %(message)s',
# level=log_level
# )
oauth2_scheme = OAuth2PasswordBearer(tokenUrl="token")
def valid_token(token):
return token == os.environ.get('API_TOKEN')

56
app/main.py Normal file
View File

@ -0,0 +1,56 @@
import datetime
import logging
import os
from fastapi import Depends, FastAPI, Request
# from fastapi.openapi.docs import get_swagger_ui_html
# from fastapi.openapi.utils import get_openapi
from .routers_v3 import current, players, results, schedules, standings, teams, transactions, battingstats, \
pitchingstats, fieldingstats, draftpicks, draftlist, managers, awards, draftdata, keepers
date = f'{datetime.datetime.now().year}-{datetime.datetime.now().month}-{datetime.datetime.now().day}'
log_level = logging.INFO if os.environ.get('LOG_LEVEL') == 'INFO' else 'WARN'
logging.basicConfig(
filename=f'logs/database/{date}.log',
format='%(asctime)s - sba-database - %(levelname)s - %(message)s',
level=log_level
)
app = FastAPI(
responses={404: {'description': 'Not found'}}
)
app.include_router(current.router)
app.include_router(players.router)
app.include_router(results.router)
app.include_router(schedules.router)
app.include_router(teams.router)
app.include_router(transactions.router)
app.include_router(standings.router)
app.include_router(battingstats.router)
app.include_router(pitchingstats.router)
app.include_router(fieldingstats.router)
app.include_router(draftpicks.router)
app.include_router(draftlist.router)
app.include_router(managers.router)
app.include_router(awards.router)
app.include_router(draftdata.router)
app.include_router(keepers.router)
# @app.get("/docs", include_in_schema=False)
# async def get_docs(req: Request):
# print(req.scope)
# return get_swagger_ui_html(openapi_url=req.scope.get('root_path')+'/openapi.json', title='Swagger')
#
#
# @app.get("/openapi.json", include_in_schema=False)
# async def openapi():
# return get_openapi(title='SBa Dev API', version=f'0.1.1', routes=app.routes)
# @app.get("/api")
# async def root():
# return {"message": "Hello Bigger Applications!"}

View File

162
app/routers_v3/awards.py Normal file
View File

@ -0,0 +1,162 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, Award, Team, Player, Manager, model_to_dict, chunked, fn
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/awards',
tags=['awards']
)
class AwardModel(pydantic.BaseModel):
name: str
season: int
timing: Optional[str] = "In-Season"
manager1_id: Optional[int] = None
manager2_id: Optional[int] = None
player_id: Optional[int] = None
team_id: Optional[int] = None
image: Optional[str] = None
class AwardList(pydantic.BaseModel):
count: int
awards: List[AwardModel]
@router.get('')
async def get_awards(
name: list = Query(default=None), season: Optional[int] = None, timing: Optional[str] = None,
manager_id: list = Query(default=None), player_id: list = Query(default=None),
team_id: list = Query(default=None), short_output: Optional[bool] = False):
all_awards = Award.select()
if name is not None:
name_list = [x.lower() for x in name]
all_awards = all_awards.where(fn.Lower(Award.name) == name_list)
if season is not None:
all_awards = all_awards.where(Award.season == season)
if timing is not None:
all_awards = all_awards.where(fn.Lower(Award.timing) == timing.lower())
if manager_id is not None:
managers = Manager.select().where(Manager.id << manager_id)
all_awards = all_awards.where(
(Award.manager1 << managers) | (Award.manager2 << managers)
)
if player_id is not None:
all_awards = all_awards.where(Award.player_id << player_id)
if team_id is not None:
all_awards = all_awards.where(Award.team_id << team_id)
return_awards = {
'count': all_awards.count(),
'awards': [model_to_dict(x, recurse=not short_output) for x in all_awards]
}
db.close()
return return_awards
@router.get('/{award_id}')
async def get_one_award(award_id: int, short_output: Optional[bool] = False):
this_award = Award.get_or_none(Award.id == award_id)
if this_award is None:
db.close()
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found')
db.close()
return model_to_dict(this_award, recurse=not short_output)
@router.patch('/{award_id}')
async def patch_award(
award_id: int, name: Optional[str] = None, season: Optional[int] = None, timing: Optional[str] = None,
image: Optional[str] = None, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None,
player_id: Optional[int] = None, team_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_player - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_award = Award.get_or_none(Award.id == award_id)
if this_award is None:
db.close()
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found')
if name is not None:
this_award.name = name
if season is not None:
this_award.season = season
if timing is not None:
this_award.timing = timing
if image is not None:
this_award.image = image
if manager1_id is not None:
this_award.manager1_id = manager1_id
if manager2_id is not None:
this_award.manager2_id = manager2_id
if player_id is not None:
this_award.player_id = player_id
if team_id is not None:
this_award.team_id = team_id
if this_award.save() == 1:
r_award = model_to_dict(this_award)
db.close()
return r_award
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch award {award_id}')
@router.post('')
async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_player - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_awards = []
for x in award_list.awards:
if x.manager1_id is not None and Manager.get_or_none(Manager.id == x.manager1_id) is None:
raise HTTPException(status_code=404, detail=f'Manager ID {x.manager1_id} not found')
if x.manager2_id is not None and Manager.get_or_none(Manager.id == x.manager2_id) is None:
raise HTTPException(status_code=404, detail=f'Manager ID {x.manager2_id} not found')
if x.player_id is not None and Player.get_or_none(Player.id == x.player_id) is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
if x.team_id is not None and Team.get_or_none(Team.id == x.team_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found')
new_awards.append(x.dict())
with db.atomic():
for batch in chunked(new_awards, 15):
Award.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_awards)} awards'
@router.delete('/{award_id}')
async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_player - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_award = Award.get_or_none(Award.id == award_id)
if this_award is None:
db.close()
raise HTTPException(status_code=404, detail=f'Award ID {award_id} not found')
count = this_award.delete_instance()
db.close()
if count == 1:
return f'Award {award_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Award {award_id} could not be deleted')

View File

@ -0,0 +1,306 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional, Literal
import logging
import pydantic
from ..db_engine import db, BattingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks
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/v3/battingstats',
tags=['battingstats']
)
class BatStatModel(pydantic.BaseModel):
player_id: int
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
xba: Optional[int] = 0
xbt: 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
roba: Optional[int] = 0
robs: Optional[int] = 0
raa: Optional[int] = 0
rto: Optional[int] = 0
week: int
game: int
season: int
class BatStatList(pydantic.BaseModel):
count: int
stats: List[BatStatModel]
@router.get('')
async def get_batstats(
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None),
player_name: list = Query(default=None), player_id: list = Query(default=None),
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
position: list = Query(default=None), limit: Optional[int] = None, sort: Optional[str] = None,
short_output: Optional[bool] = True):
if 'post' in s_type.lower():
all_stats = BattingStat.post_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
elif s_type.lower() in ['combined', 'total', 'all']:
all_stats = BattingStat.combined_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
else:
all_stats = BattingStat.regular_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
if position is not None:
all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position])
if team_abbrev is not None:
t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev])
all_stats = all_stats.where(BattingStat.team << t_query)
if player_name is not None or player_id is not None:
if player_id:
all_stats = all_stats.where(BattingStat.player_id << player_id)
else:
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name])
all_stats = all_stats.where(BattingStat.player << p_query)
if game_num:
all_stats = all_stats.where(BattingStat.game == game_num)
start = 1
end = Current.get(Current.season == season).week
if week_start is not None:
start = week_start
if week_end is not None:
end = min(week_end, end)
if start > end:
db.close()
raise HTTPException(
status_code=404,
detail=f'Start week {start} is after end week {end} - cannot pull stats'
)
all_stats = all_stats.where(
(BattingStat.week >= start) & (BattingStat.week <= end)
)
if limit:
all_stats = all_stats.limit(limit)
if sort:
if sort == 'newest':
all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game)
return_stats = {
'count': all_stats.count(),
'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats],
# 'stats': [{'id': x.id} for x in all_stats]
}
db.close()
return return_stats
@router.get('/totals')
async def get_totalstats(
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None),
team_id: list = Query(default=None), player_name: list = Query(default=None),
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
position: list = Query(default=None), sort: Optional[str] = None, player_id: list = Query(default=None),
group_by: Literal['team', 'player', 'playerteam'] = 'player', short_output: Optional[bool] = False,
min_pa: Optional[int] = 1, week: list = Query(default=None)):
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.')
all_stats = (
BattingStat
.select(BattingStat.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.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.so).alias('sum_so'),
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'),
fn.SUM(BattingStat.xba).alias('sum_xba'), fn.SUM(BattingStat.xbt).alias('sum_xbt'),
fn.SUM(BattingStat.xch).alias('sum_xch'), fn.SUM(BattingStat.xhit).alias('sum_xhit'),
fn.SUM(BattingStat.error).alias('sum_error'), fn.SUM(BattingStat.pb).alias('sum_pb'),
fn.SUM(BattingStat.sbc).alias('sum_sbc'), fn.SUM(BattingStat.csc).alias('sum_csc'),
fn.SUM(BattingStat.roba).alias('sum_roba'), fn.SUM(BattingStat.robs).alias('sum_robs'),
fn.SUM(BattingStat.raa).alias('sum_raa'), fn.SUM(BattingStat.rto).alias('sum_rto'),
BattingStat.team)
.where(BattingStat.season == season)
.having(fn.SUM(BattingStat.pa) >= min_pa)
)
if True in [s_type is not None, week_start is not None, week_end is not None]:
weeks = {}
if s_type is not None:
weeks = per_season_weeks(season, s_type)
elif week_start is not None or week_end is not None:
if week_start is None or week_end is None:
raise HTTPException(
status_code=400, detail='Both week_start and week_end must be included if either is used.'
)
weeks['start'] = week_start
if week_end < weeks['start']:
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
else:
weeks['end'] = week_end
all_stats = all_stats.where(
(BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end'])
)
elif week is not None:
all_stats = all_stats.where(BattingStat.week << week)
if game_num is not None:
all_stats = all_stats.where(BattingStat.game << game_num)
if position is not None:
p_list = [x.upper() for x in position]
all_players = Player.select().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)
)
all_stats = all_stats.where(BattingStat.player << all_players)
if sort is not None:
if sort == 'player':
all_stats = all_stats.order_by(BattingStat.player)
elif sort == 'team':
all_stats = all_stats.order_by(BattingStat.team)
if group_by is not None:
if group_by == 'team':
all_stats = all_stats.group_by(BattingStat.team)
elif group_by == 'player':
all_stats = all_stats.group_by(BattingStat.player)
elif group_by == 'playerteam':
all_stats = all_stats.group_by(BattingStat.team, BattingStat.player)
# if team_abbrev is None and team_id is None and player_name is None and player_id is None:
# raise HTTPException(
# status_code=400,
# detail=f'Must include team_id/team_abbrev and/or player_name/player_id'
# )
if team_id is not None:
all_teams = Team.select().where(Team.id << team_id)
all_stats = all_stats.where(BattingStat.team << all_teams)
elif team_abbrev is not None:
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
all_stats = all_stats.where(BattingStat.team << all_teams)
if player_name is not None:
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name])
all_stats = all_stats.where(BattingStat.player << all_players)
elif player_id is not None:
all_players = Player.select().where(Player.id << player_id)
all_stats = all_stats.where(BattingStat.player << all_players)
return_stats = {
'count': all_stats.count(),
'stats': [{
'player': x.player_id if short_output else model_to_dict(x.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
} for x in all_stats]
}
db.close()
return return_stats
# @router.get('/career/{player_name}')
# async def get_careerstats(
# s_type: Literal['regular', 'post', 'total'] = 'regular', player_name: list = Query(default=None)):
# pass # Keep Career Stats table and recalculate after posting stats
@router.patch('/{stat_id}')
async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_batstats - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
if BattingStat.get_or_none(BattingStat.id == stat_id) is None:
raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found')
BattingStat.update(**new_stats.dict()).where(BattingStat.id == stat_id).execute()
r_stat = model_to_dict(BattingStat.get_by_id(stat_id))
db.close()
return r_stat
@router.post('')
async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_batstats - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
all_stats = []
for x in s_list.stats:
team = Team.get_or_none(Team.id == x.team_id)
this_player = Player.get_or_none(Player.id == x.player_id)
if team is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found')
if this_player is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
all_stats.append(BattingStat(**x.dict()))
with db.atomic():
for batch in chunked(all_stats, 15):
BattingStat.insert_many(batch).on_conflict_replace().execute()
# Update career stats
db.close()
return f'Added {len(all_stats)} batting lines'

118
app/routers_v3/current.py Normal file
View File

@ -0,0 +1,118 @@
from fastapi import APIRouter, Depends, HTTPException
from typing import Optional
import logging
import pydantic
from ..db_engine import db, Current, model_to_dict
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/current',
tags=['current']
)
class CurrentModel(pydantic.BaseModel):
week: Optional[int] = 0
freeze: Optional[bool] = False
season: int
transcount: Optional[int] = 0
bstatcount: Optional[int] = 0
pstatcount: Optional[int] = 0
bet_week: Optional[int] = 0
trade_deadline: int
pick_trade_start: int = 69
pick_trade_end: int = 420
playoffs_begin: int
injury_count: Optional[int] = 0
@router.get('')
async def get_current(season: Optional[int] = None):
if season is not None:
current = Current.get_or_none(season=season)
else:
current = Current.latest()
if current is not None:
r_curr = model_to_dict(current)
db.close()
return r_curr
else:
return None
@router.patch('/{current_id}')
async def patch_current(
current_id: int, season: Optional[int] = None, week: Optional[int] = None, freeze: Optional[bool] = None,
transcount: Optional[int] = None, bstatcount: Optional[int] = None, pstatcount: Optional[int] = None,
bet_week: Optional[int] = None, trade_deadline: Optional[int] = None, pick_trade_start: Optional[int] = None,
pick_trade_end: Optional[int] = None, injury_count: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_current - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
try:
current = Current.get_by_id(current_id)
except Exception as e:
raise HTTPException(status_code=404, detail=f'Current id {current_id} not found')
if week is not None:
current.week = week
if season is not None:
current.season = season
if freeze is not None:
current.freeze = freeze
if transcount is not None:
current.transcount = transcount
if bstatcount is not None:
current.bstatcount = bstatcount
if pstatcount is not None:
current.pstatcount = pstatcount
if bet_week is not None:
current.bet_week = bet_week
if trade_deadline is not None:
current.trade_deadline = trade_deadline
if pick_trade_start is not None:
current.pick_trade_start = pick_trade_start
if pick_trade_end is not None:
current.pick_trade_end = pick_trade_end
if injury_count is not None:
current.injury_count = injury_count
if current.save():
r_curr = model_to_dict(current)
db.close()
return r_curr
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch current {current_id}')
@router.post('')
async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_current - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_current = Current(**new_current.dict())
if this_current.save():
r_curr = model_to_dict(this_current)
db.close()
return r_curr
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to post season {new_current.season} current')
@router.delete('/{current_id}')
async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_current - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
if Current.delete_by_id(current_id) == 1:
return f'Deleted current ID {current_id}'
raise HTTPException(status_code=500, detail=f'Unable to delete current {current_id}')

View File

@ -0,0 +1,73 @@
import datetime
from fastapi import APIRouter, Depends, HTTPException
from typing import Optional
import logging
import pydantic
from ..db_engine import db, DraftData, model_to_dict
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/draftdata',
tags=['draftdata']
)
class DraftDataModel(pydantic.BaseModel):
currentpick: int
timer: bool
pick_deadline: datetime.datetime
result_channel_id = int
ping_channel_id = int
pick_minutes = int
@router.get('')
async def get_draftdata():
draft_data = DraftData.get_or_none()
if draft_data is not None:
r_data = model_to_dict(draft_data)
db.close()
return r_data
raise HTTPException(status_code=404, detail=f'No draft data found')
@router.patch('/{data_id}')
async def patch_draftdata(
data_id: int, currentpick: Optional[int] = None, timer: Optional[bool] = None,
pick_deadline: Optional[datetime.datetime] = None, result_channel: Optional[int] = None,
ping_channel: Optional[int] = None, pick_minutes: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_draftdata - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
draft_data = DraftData.get_or_none(DraftData.id == data_id)
if draft_data is None:
db.close()
raise HTTPException(status_code=404, detail=f'No draft data found')
if currentpick is not None:
draft_data.currentpick = currentpick
if timer is not None:
draft_data.timer = timer
if pick_deadline is not None:
draft_data.pick_deadline = pick_deadline
if result_channel is not None:
draft_data.result_channel = result_channel
if ping_channel is not None:
draft_data.ping_channel = ping_channel
if pick_minutes is not None:
draft_data.pick_minutes = pick_minutes
saved = draft_data.save()
r_data = model_to_dict(draft_data)
db.close()
if saved == 1:
return r_data
else:
raise HTTPException(status_code=500, detail='Updating draft data failed')

View File

@ -0,0 +1,91 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, DraftList, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/draftlist',
tags=['draftlist']
)
class DraftListModel(pydantic.BaseModel):
season: int
team_id: int
rank: int
player_id: int
class DraftListList(pydantic.BaseModel):
count: int
draft_list: List[DraftListModel]
@router.get('')
async def get_draftlist(
season: Optional[int], team_id: list = Query(default=None), token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'get_draftlist - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
all_list = DraftList.select()
if season is not None:
all_list = all_list.where(DraftList.season == season)
if team_id is not None:
all_list = all_list.where(DraftList.team_id << team_id)
r_list = {
'count': all_list.count(),
'picks': [model_to_dict(x) for x in all_list]
}
db.close()
return r_list
@router.get('/team/{team_id}')
async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_draftlist - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_team = Team.get_or_none(Team.id == team_id)
if this_team is None:
raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found')
this_list = DraftList.select().where(DraftList.team == this_team)
r_list = {
'count': this_list.count(),
'picks': [model_to_dict(x) for x in this_list]
}
db.close()
return r_list
@router.post('')
async def post_draftlist(draft_list: DraftListList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_draftlist - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_list = []
this_team = Team.get_or_none(Team.id == draft_list.draft_list[0].team_id)
if this_team is None:
raise HTTPException(status_code=404, detail=f'Team ID {draft_list.draft_list[0].team_id} not found')
DraftList.delete().where(DraftList.team == this_team).execute()
for x in draft_list.draft_list:
new_list.append(x.dict())
with db.atomic():
for batch in chunked(new_list, 15):
DraftList.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_list)} list values'

View File

@ -0,0 +1,169 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, DraftPick, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/draftpicks',
tags=['draftpicks']
)
class DraftPickModel(pydantic.BaseModel):
overall: Optional[int] = None
round: int
origowner_id: int
owner_id: Optional[int] = None
season: int
player_id: Optional[int] = None
class DraftPickList(pydantic.BaseModel):
picks: List[DraftPickModel]
@router.get('')
async def get_picks(
season: int, owner_team_abbrev: list = Query(default=None), orig_team_abbrev: list = Query(default=None),
owner_team_id: list = Query(default=None), orig_team_id: list = Query(default=None),
pick_round_start: Optional[int] = None, pick_round_end: Optional[int] = None, traded: Optional[bool] = None,
overall: Optional[int] = None, overall_start: Optional[int] = None, overall_end: Optional[int] = None,
short_output: Optional[bool] = False, sort: Optional[str] = None, limit: Optional[int] = None,
player_id: list = Query(default=None), player_taken: Optional[bool] = None):
all_picks = DraftPick.select().where(DraftPick.season == season)
if owner_team_abbrev is not None:
team_list = []
for x in owner_team_abbrev:
team_list.append(Team.get_season(x, season))
all_picks = all_picks.where(
(DraftPick.owner << team_list) | (DraftPick.owner << team_list)
)
if orig_team_abbrev is not None:
team_list = []
for x in orig_team_abbrev:
team_list.append(Team.get_season(x, season))
all_picks = all_picks.where(
(DraftPick.origowner << team_list) | (DraftPick.origowner << team_list)
)
if owner_team_id is not None:
all_picks = all_picks.where(
(DraftPick.owner_id << owner_team_id) | (DraftPick.owner_id << owner_team_id)
)
if orig_team_id is not None:
all_picks = all_picks.where(
(DraftPick.origowner_id << orig_team_id) | (DraftPick.origowner_id << orig_team_id)
)
if pick_round_start is not None and pick_round_end is not None and pick_round_end < pick_round_start:
raise HTTPException(status_code=400, detail=f'pick_round_end must be greater than or equal to pick_round_start')
if player_id is not None:
all_picks = all_picks.where(DraftPick.player_id << player_id)
if pick_round_start is not None:
all_picks = all_picks.where(DraftPick.round >= pick_round_start)
if pick_round_end is not None:
all_picks = all_picks.where(DraftPick.round <= pick_round_end)
if traded is not None:
all_picks = all_picks.where(DraftPick.origowner != DraftPick.owner)
if overall is not None:
all_picks = all_picks.where(DraftPick.overall == overall)
if overall_start is not None:
all_picks = all_picks.where(DraftPick.overall >= overall_start)
if overall_end is not None:
all_picks = all_picks.where(DraftPick.overall <= overall_end)
if player_taken is not None:
all_picks = all_picks.where(DraftPick.player.is_null(not player_taken))
if limit is not None:
all_picks = all_picks.limit(limit)
if sort is not None:
if sort == 'order-asc':
all_picks = all_picks.order_by(DraftPick.overall)
elif sort == 'order-desc':
all_picks = all_picks.order_by(-DraftPick.overall)
return_picks = {'count': all_picks.count(), 'picks': []}
for line in all_picks:
return_picks['picks'].append(model_to_dict(line, recurse=not short_output))
db.close()
return return_picks
@router.get('/{pick_id}')
async def get_one_pick(pick_id: int, short_output: Optional[bool] = False):
this_pick = DraftPick.get_or_none(DraftPick.id == pick_id)
if this_pick is not None:
r_pick = model_to_dict(this_pick, recurse=not short_output)
else:
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found')
db.close()
return r_pick
@router.patch('/{pick_id}')
async def patch_pick(pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_pick - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
if DraftPick.get_or_none(DraftPick.id == pick_id) is None:
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found')
DraftPick.update(**new_pick.dict()).where(DraftPick.id == pick_id).execute()
r_pick = model_to_dict(DraftPick.get_by_id(pick_id))
db.close()
return r_pick
@router.post('')
async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_picks - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_picks = []
for pick in p_list.picks:
dupe = DraftPick.get_or_none(DraftPick.season == pick.season, DraftPick.overall == pick.overall)
if dupe:
db.close()
raise HTTPException(
status_code=500,
detail=f'Pick # {pick.overall} already exists for season {pick.season}'
)
new_picks.append(pick.dict())
with db.atomic():
for batch in chunked(new_picks, 15):
DraftPick.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_picks)} picks'
@router.delete('/{pick_id}')
async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_pick - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_pick = DraftPick.get_or_none(DraftPick.id == pick_id)
if this_pick is None:
raise HTTPException(status_code=404, detail=f'Pick ID {pick_id} not found')
count = this_pick.delete_instance()
db.close()
if count == 1:
return f'Draft pick {pick_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Draft pick {pick_id} could not be deleted')

View File

@ -0,0 +1,193 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional, Literal
import logging
import pydantic
from ..db_engine import db, BattingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks
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/v3/fieldingstats',
tags=['fieldingstats']
)
@router.get('')
async def get_fieldingstats(
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None),
player_name: list = Query(default=None), player_id: list = Query(default=None),
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
position: list = Query(default=None), limit: Optional[int] = None, sort: Optional[str] = None,
short_output: Optional[bool] = True):
if 'post' in s_type.lower():
all_stats = BattingStat.post_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
elif s_type.lower() in ['combined', 'total', 'all']:
all_stats = BattingStat.combined_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
else:
all_stats = BattingStat.regular_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
all_stats = all_stats.where(
(BattingStat.xch > 0) | (BattingStat.pb > 0) | (BattingStat.sbc > 0)
)
if position is not None:
all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position])
if team_abbrev is not None:
t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev])
all_stats = all_stats.where(BattingStat.team << t_query)
if player_name is not None or player_id is not None:
if player_id:
all_stats = all_stats.where(BattingStat.player_id << player_id)
else:
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name])
all_stats = all_stats.where(BattingStat.player << p_query)
if game_num:
all_stats = all_stats.where(BattingStat.game == game_num)
start = 1
end = Current.get(Current.season == season).week
if week_start is not None:
start = week_start
if week_end is not None:
end = min(week_end, end)
if start > end:
db.close()
raise HTTPException(
status_code=404,
detail=f'Start week {start} is after end week {end} - cannot pull stats'
)
all_stats = all_stats.where(
(BattingStat.week >= start) & (BattingStat.week <= end)
)
if limit:
all_stats = all_stats.limit(limit)
if sort:
if sort == 'newest':
all_stats = all_stats.order_by(-BattingStat.week, -BattingStat.game)
return_stats = {
'count': all_stats.count(),
'stats': [{
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
'pos': x.pos,
'xch': x.xch,
'xhit': x.xhit,
'error': x.error,
'pb': x.pb,
'sbc': x.sbc,
'csc': x.csc,
'week': x.week,
'game': x.game,
'season': x.season
} for x in all_stats]
}
db.close()
return return_stats
@router.get('/totals')
async def get_totalstats(
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None),
team_id: list = Query(default=None), player_name: list = Query(default=None),
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
position: list = Query(default=None), sort: Optional[str] = None, player_id: list = Query(default=None),
group_by: Literal['team', 'player', 'playerteam'] = 'player', short_output: Optional[bool] = False,
min_ch: Optional[int] = 1, week: list = Query(default=None)):
all_stats = (
BattingStat
.select(BattingStat.player, BattingStat.pos, fn.SUM(BattingStat.xch).alias('sum_xch'),
fn.SUM(BattingStat.xhit).alias('sum_xhit'), fn.SUM(BattingStat.error).alias('sum_error'),
fn.SUM(BattingStat.pb).alias('sum_pb'), fn.SUM(BattingStat.sbc).alias('sum_sbc'),
fn.SUM(BattingStat.csc).alias('sum_csc'), BattingStat.team)
.where(BattingStat.season == season)
.having(fn.SUM(BattingStat.xch) >= min_ch)
)
if True in [s_type is not None, week_start is not None, week_end is not None]:
weeks = {}
if s_type is not None:
weeks = per_season_weeks(season, s_type)
elif week_start is not None or week_end is not None:
if week_start is None or week_end is None:
raise HTTPException(
status_code=400, detail='Both week_start and week_end must be included if either is used.'
)
weeks['start'] = week_start
if week_end < weeks['start']:
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
else:
weeks['end'] = week_end
all_stats = all_stats.where(
(BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end'])
)
elif week is not None:
all_stats = all_stats.where(BattingStat.week << week)
if game_num is not None:
all_stats = all_stats.where(BattingStat.game << game_num)
if position is not None:
p_list = [x.upper() for x in position]
all_players = Player.select().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)
)
all_stats = all_stats.where(BattingStat.player << all_players)
if sort is not None:
if sort == 'player':
all_stats = all_stats.order_by(BattingStat.player)
elif sort == 'team':
all_stats = all_stats.order_by(BattingStat.team)
if group_by is not None:
if group_by == 'team':
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team)
elif group_by == 'player':
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.player)
elif group_by == 'playerteam':
all_stats = all_stats.group_by(BattingStat.pos, BattingStat.team, BattingStat.player)
if team_id is not None:
all_teams = Team.select().where(Team.id << team_id)
all_stats = all_stats.where(BattingStat.team << all_teams)
elif team_abbrev is not None:
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
all_stats = all_stats.where(BattingStat.team << all_teams)
if player_name is not None:
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name])
all_stats = all_stats.where(BattingStat.player << all_players)
elif player_id is not None:
all_players = Player.select().where(Player.id << player_id)
all_stats = all_stats.where(BattingStat.player << all_players)
return_stats = {
'count': sum(1 for i in all_stats if i.sum_xch + i.sum_sbc > 0),
'stats': [{
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
'pos': x.pos,
'xch': x.sum_xch,
'xhit': x.sum_xhit,
'error': x.sum_error,
'pb': x.sum_pb,
'sbc': x.sum_sbc,
'csc': x.sum_csc
} for x in all_stats if x.sum_xch + x.sum_sbc > 0]
}
db.close()
return return_stats

113
app/routers_v3/keepers.py Normal file
View File

@ -0,0 +1,113 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, Keeper, Player, model_to_dict, chunked, 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/v3/keepers',
tags=['keepers']
)
class KeeperModel(pydantic.BaseModel):
season: int
team_id: int
player_id: int
class KeeperList(pydantic.BaseModel):
count: Optional[int] = None
keepers: List[KeeperModel]
@router.get('')
async def get_keepers(
season: list = Query(default=None), team_id: list = Query(default=None), player_id: list = Query(default=None),
short_output: bool = False):
all_keepers = Keeper.select()
if season is not None:
all_keepers = all_keepers.where(Keeper.season << season)
if team_id is not None:
all_keepers = all_keepers.where(Keeper.team_id << team_id)
if player_id is not None:
all_keepers = all_keepers.where(Keeper.player_id << player_id)
return_keepers = {
'count': all_keepers.count(),
'keepers': [model_to_dict(x, recurse=not short_output) for x in all_keepers]
}
db.close()
return return_keepers
@router.patch('/{keeper_id}')
async def patch_keeper(
keeper_id: int, season: Optional[int] = None, team_id: Optional[int] = None, player_id: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_keeper - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
if not this_keeper:
raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found')
if season is not None:
this_keeper.season = season
if player_id is not None:
this_keeper.player_id = player_id
if team_id is not None:
this_keeper.team_id = team_id
if this_keeper.save():
r_keeper = model_to_dict(this_keeper)
db.close()
return r_keeper
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch keeper {keeper_id}')
@router.post('')
async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_keepers - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_keepers = []
for keeper in k_list.keepers:
new_keepers.append(keeper.dict())
with db.atomic():
for batch in chunked(new_keepers, 14):
Keeper.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_keepers)} keepers'
@router.delete('/{keeper_id}')
async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_keeper - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_keeper = Keeper.get_or_none(Keeper.id == keeper_id)
if not this_keeper:
raise HTTPException(status_code=404, detail=f'Keeper ID {keeper_id} not found')
count = this_keeper.delete_instance()
db.close()
if count == 1:
return f'Keeper ID {keeper_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Keeper ID {keeper_id} could not be deleted')

152
app/routers_v3/managers.py Normal file
View File

@ -0,0 +1,152 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, Manager, Team, Current, model_to_dict, fn
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/managers',
tags=['managers']
)
class ManagerModel(pydantic.BaseModel):
name: str
image: Optional[str] = None
headline: Optional[str] = None
bio: Optional[str] = None
@router.get('')
async def get_managers(
name: list = Query(default=None), active: Optional[bool] = None, short_output: Optional[bool] = False):
if active is not None:
current = Current.latest()
t_query = Team.select_season(current.season)
t_query = t_query.where(
~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL'))
)
logging.info(f'tquery: {t_query}')
a_mgr = []
i_mgr = []
for x in t_query:
logging.info(f'Team: {x.abbrev} / mgr1: {x.manager1} / mgr2: {x.manager2}')
if x.manager1 is not None:
a_mgr.append(x.manager1)
logging.info(f'appending {x.manager1.name}')
if x.manager2 is not None:
a_mgr.append(x.manager2)
logging.info(f'appending {x.manager2.name}')
logging.info(f'a_mgr: {a_mgr}')
if active:
final_mgrs = [model_to_dict(y, recurse=not short_output) for y in a_mgr]
else:
logging.info(f'checking inactive')
for z in Manager.select():
logging.info(f'checking: {z.name}')
if z not in a_mgr:
logging.info(f'+inactive: {z.name}')
i_mgr.append(z)
final_mgrs = [model_to_dict(y, recurse=not short_output) for y in i_mgr]
return_managers = {
'count': len(final_mgrs),
'managers': final_mgrs
}
else:
all_managers = Manager.select()
if name is not None:
name_list = [x.lower() for x in name]
all_managers = all_managers.where(fn.Lower(Manager.name) << name_list)
return_managers = {
'count': all_managers.count(),
'managers': [model_to_dict(x, recurse=not short_output) for x in all_managers]
}
db.close()
return return_managers
@router.get('/{manager_id}')
async def get_one_manager(manager_id: int, short_output: Optional[bool] = False):
this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is not None:
r_manager = model_to_dict(this_manager, recurse=not short_output)
db.close()
return r_manager
else:
raise HTTPException(status_code=404, detail=f'Manager {manager_id} not found')
@router.patch('/{manager_id}')
async def patch_manager(
manager_id: int, name: Optional[str] = None, image: Optional[str] = None, headline: Optional[str] = None,
bio: Optional[str] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_manager - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is None:
db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found')
if name is not None:
this_manager.name = name
if image is not None:
this_manager.image = image
if headline is not None:
this_manager.headline = headline
if bio is not None:
this_manager.bio = bio
if this_manager.save() == 1:
r_manager = model_to_dict(this_manager)
db.close()
return r_manager
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch manager {this_manager}')
@router.post('')
async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_manager - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_manager = Manager(**new_manager.dict())
if this_manager.save():
r_manager = model_to_dict(this_manager)
db.close()
return r_manager
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to post manager {this_manager.name}')
@router.delete('/{manager_id}')
async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_manager - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_manager = Manager.get_or_none(Manager.id == manager_id)
if this_manager is None:
db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {manager_id} not found')
count = this_manager.delete_instance()
db.close()
if count == 1:
return f'Manager {manager_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Manager {manager_id} could not be deleted')

View File

@ -0,0 +1,267 @@
import datetime
import os
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional, Literal
import logging
import pydantic
from ..db_engine import db, PitchingStat, Team, Player, Current, model_to_dict, chunked, fn, per_season_weeks
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/v3/pitchingstats',
tags=['pitchingstats']
)
class PitStatModel(pydantic.BaseModel):
player_id: int
team_id: int
ip: Optional[float] = 0.0
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
gs: Optional[int] = 0
win: Optional[int] = 0
loss: Optional[int] = 0
hold: Optional[int] = 0
sv: Optional[int] = 0
bsv: Optional[int] = 0
ir: Optional[int] = 0
irs: Optional[int] = 0
week: int
game: int
season: int
class PitStatList(pydantic.BaseModel):
count: int
stats: List[PitStatModel]
@router.get('')
async def get_pitstats(
season: int, s_type: Optional[str] = 'regular', team_abbrev: list = Query(default=None),
player_name: list = Query(default=None), player_id: list = Query(default=None),
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
limit: Optional[int] = None, ip_min: Optional[float] = None, sort: Optional[str] = None,
short_output: Optional[bool] = True):
if 'post' in s_type.lower():
all_stats = PitchingStat.post_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
elif s_type.lower() in ['combined', 'total', 'all']:
all_stats = PitchingStat.combined_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
else:
all_stats = PitchingStat.regular_season(season)
if all_stats.count() == 0:
db.close()
return {'count': 0, 'stats': []}
if team_abbrev is not None:
t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev])
all_stats = all_stats.where(PitchingStat.team << t_query)
if player_name is not None or player_id is not None:
if player_id:
all_stats = all_stats.where(PitchingStat.player_id << player_id)
else:
p_query = Player.select_season(season).where(fn.Lower(Player.name) << [x.lower() for x in player_name])
all_stats = all_stats.where(PitchingStat.player << p_query)
if game_num:
all_stats = all_stats.where(PitchingStat.game == game_num)
if ip_min is not None:
all_stats = all_stats.where(PitchingStat.ip >= ip_min)
start = 1
end = Current.get(Current.season == season).week
if week_start is not None:
start = week_start
if week_end is not None:
end = min(week_end, end)
if start > end:
db.close()
raise HTTPException(
status_code=404,
detail=f'Start week {start} is after end week {end} - cannot pull stats'
)
all_stats = all_stats.where(
(PitchingStat.week >= start) & (PitchingStat.week <= end)
)
if limit:
all_stats = all_stats.limit(limit)
if sort:
if sort == 'newest':
all_stats = all_stats.order_by(-PitchingStat.week, -PitchingStat.game)
return_stats = {
'count': all_stats.count(),
'stats': [model_to_dict(x, recurse=not short_output) for x in all_stats]
}
db.close()
return return_stats
@router.get('/totals')
async def get_totalstats(
season: int, s_type: Literal['regular', 'post', 'total', None] = None, team_abbrev: list = Query(default=None),
team_id: list = Query(default=None), player_name: list = Query(default=None),
week_start: Optional[int] = None, week_end: Optional[int] = None, game_num: list = Query(default=None),
is_sp: Optional[bool] = None, ip_min: Optional[float] = 0.25, sort: Optional[str] = None,
player_id: list = Query(default=None), short_output: Optional[bool] = False,
group_by: Literal['team', 'player', 'playerteam'] = 'player', week: list = Query(default=None)):
if sum(1 for x in [s_type, (week_start or week_end), week] if x is not None) > 1:
raise HTTPException(status_code=400, detail=f'Only one of s_type, week_start/week_end, or week may be used.')
all_stats = (
PitchingStat
.select(PitchingStat.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.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.SUM(PitchingStat.irs).alias('sum_irs'),
fn.SUM(PitchingStat.gs).alias('sum_gs'), PitchingStat.team)
.where(PitchingStat.season == season)
.having(fn.SUM(PitchingStat.ip) >= ip_min)
)
if True in [s_type is not None, week_start is not None, week_end is not None]:
weeks = {}
if s_type is not None:
weeks = per_season_weeks(season, s_type)
elif week_start is not None or week_end is not None:
if week_start is None or week_end is None:
raise HTTPException(
status_code=400, detail='Both week_start and week_end must be included if either is used.'
)
weeks['start'] = week_start
if week_end < weeks['start']:
raise HTTPException(status_code=400, detail='week_end must be greater than or equal to week_start')
else:
weeks['end'] = week_end
all_stats = all_stats.where(
(PitchingStat.week >= weeks['start']) & (PitchingStat.week <= weeks['end'])
)
elif week is not None:
all_stats = all_stats.where(PitchingStat.week << week)
if game_num is not None:
all_stats = all_stats.where(PitchingStat.game << game_num)
if is_sp is not None:
if is_sp:
all_stats = all_stats.where(PitchingStat.gs == 1)
if not is_sp:
all_stats = all_stats.where(PitchingStat.gs == 0)
if sort is not None:
if sort == 'player':
all_stats = all_stats.order_by(PitchingStat.player)
elif sort == 'team':
all_stats = all_stats.order_by(PitchingStat.team)
if group_by is not None:
if group_by == 'team':
all_stats = all_stats.group_by(PitchingStat.team)
elif group_by == 'player':
all_stats = all_stats.group_by(PitchingStat.player)
elif group_by == 'playerteam':
all_stats = all_stats.group_by(PitchingStat.team, PitchingStat.player)
if team_id is not None:
all_teams = Team.select().where(Team.id << team_id)
all_stats = all_stats.where(PitchingStat.team << all_teams)
elif team_abbrev is not None:
all_teams = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
all_stats = all_stats.where(PitchingStat.team << all_teams)
if player_name is not None:
all_players = Player.select().where(fn.Lower(Player.name) << [x.lower() for x in player_name])
all_stats = all_stats.where(PitchingStat.player << all_players)
elif player_id is not None:
all_players = Player.select().where(Player.id << player_id)
all_stats = all_stats.where(PitchingStat.player << all_players)
return_stats = {
'count': all_stats.count(),
'stats': [{
'player': x.player_id if short_output else model_to_dict(x.player, recurse=False),
'team': x.team_id if short_output else model_to_dict(x.team, recurse=False),
'ip': x.sum_ip,
'hit': x.sum_hit,
'run': x.sum_run,
'erun': x.sum_erun,
'so': x.sum_so,
'bb': x.sum_bb,
'hbp': x.sum_hbp,
'wp': x.sum_wp,
'balk': x.sum_balk,
'hr': x.sum_hr,
'ir': x.sum_ir,
'irs': x.sum_irs,
'gs': x.sum_gs,
'win': x.sum_win,
'loss': x.sum_loss,
'hold': x.sum_hold,
'sv': x.sum_sv,
'bsv': x.sum_bsv
} for x in all_stats]
}
db.close()
return return_stats
@router.patch('/{stat_id}')
async def patch_pitstats(stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_pitstats - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
if PitchingStat.get_or_none(PitchingStat.id == stat_id) is None:
raise HTTPException(status_code=404, detail=f'Stat ID {stat_id} not found')
PitchingStat.update(**new_stats.dict()).where(PitchingStat.id == stat_id).execute()
r_stat = model_to_dict(PitchingStat.get_by_id(stat_id))
db.close()
return r_stat
@router.post('')
async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_pitstats - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
all_stats = []
for x in s_list.stats:
team = Team.get_or_none(Team.id == x.team_id)
this_player = Player.get_or_none(Player.id == x.player_id)
if team is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.team_id} not found')
if this_player is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
all_stats.append(PitchingStat(**x.dict()))
with db.atomic():
for batch in chunked(all_stats, 15):
PitchingStat.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Added {len(all_stats)} batting lines'

190
app/routers_v3/players.py Normal file
View File

@ -0,0 +1,190 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import List, Optional
import logging
import pydantic
from pandas import DataFrame
from ..db_engine import db, Player, model_to_dict, chunked, 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/v3/players',
tags=['players']
)
class PlayerModel(pydantic.BaseModel):
name: str
wara: float
image: str
image2: Optional[str] = None
team_id: int
season: int
pitcher_injury: Optional[int] = None
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
vanity_card: Optional[str] = None
headshot: Optional[str] = None
last_game: Optional[str] = None
last_game2: Optional[str] = None
il_return: Optional[str] = None
demotion_week: Optional[int] = None
strat_code: Optional[str] = None
bbref_id: Optional[str] = None
injury_rating: Optional[str] = None
class PlayerList(pydantic.BaseModel):
players: List[PlayerModel]
@router.get('')
async def get_players(
season: Optional[int], name: Optional[str] = None, team_id: list = Query(default=None),
pos: list = Query(default=None), strat_code: list = Query(default=None), is_injured: Optional[bool] = None,
sort: Optional[str] = None, short_output: Optional[bool] = False, csv: Optional[bool] = False):
all_players = Player.select_season(season)
if team_id is not None:
all_players = all_players.where(Player.team_id << team_id)
if strat_code is not None:
code_list = [x.lower() for x in strat_code]
all_players = all_players.where(fn.Lower(Player.strat_code) << code_list)
if name is not None:
all_players = all_players.where(fn.lower(Player.name) == name.lower())
if pos is not None:
p_list = [x.upper() for x in pos]
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 is_injured is not None:
all_players = all_players.where(Player.il_return.is_null(False))
if sort is not None:
if sort == 'cost-asc':
all_players = all_players.order_by(Player.wara)
elif sort == 'cost-desc':
all_players = all_players.order_by(-Player.wara)
elif sort == 'name-asc':
all_players = all_players.order_by(Player.name)
elif sort == 'name-desc':
all_players = all_players.order_by(-Player.name)
print(f'csv: {csv}')
if csv:
player_list = [
['name', 'wara', 'image', 'image2', 'team', 'season', 'pitcher_injury', 'pos_1', 'pos_2', 'pos_3',
'pos_4', 'pos_5', 'pos_6', 'pos_7', 'pos_8', 'last_game', 'last_game2', 'il_return', 'demotion_week',
'headshot', 'vanity_card', 'strat_code', 'bbref_id', 'injury_rating', 'player_id']
]
for line in all_players:
player_list.append(
[
line.name, line.wara, line.image, line.image2, line.team.abbrev, line.season, line.pitcher_injury,
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.last_game, line.last_game2, line.il_return, line.demotion_week, line.headshot,
line.vanity_card, line.strat_code.replace(",", "-_-"), line.bbref_id, line.injury_rating, line.id
]
)
return_players = {
'count': all_players.count(),
'players': DataFrame(player_list).to_csv(header=False, index=False),
'csv': True
}
db.close()
return Response(content=return_players['players'], media_type='text/csv')
else:
return_players = {
'count': all_players.count(),
'players': [model_to_dict(x, recurse=not short_output) for x in all_players]
}
db.close()
return return_players
@router.get('/{player_id}')
async def get_one_player(player_id: int, short_output: Optional[bool] = False):
this_player = Player.get_or_none(Player.id == player_id)
if this_player:
r_player = model_to_dict(this_player, recurse=not short_output)
else:
r_player = None
db.close()
return r_player
@router.patch('/{player_id}')
async def patch_player(
player_id: int, new_player: PlayerModel, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_player - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
if Player.get_or_none(Player.id == player_id) is None:
raise HTTPException(status_code=404, detail=f'Player ID {player_id} not found')
Player.update(**new_player.dict()).where(Player.id == player_id).execute()
r_player = model_to_dict(Player.get_by_id(player_id))
db.close()
return r_player
@router.post('')
async def post_players(p_list: PlayerList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_players - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_players = []
for player in p_list.players:
dupe = Player.get_or_none(Player.season == player.season, Player.name == player.name)
if dupe:
db.close()
raise HTTPException(
status_code=500,
detail=f'Player name {player.name} already in use in Season {player.season}'
)
new_players.append(player.dict())
with db.atomic():
for batch in chunked(new_players, 15):
Player.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_players)} 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'delete_player - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_player = Player.get_or_none(Player.id == player_id)
if not this_player:
db.close()
raise HTTPException(status_code=404, detail=f'Player ID {player_id} not found')
count = this_player.delete_instance()
db.close()
if count == 1:
return f'Player {player_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Player {player_id} could not be deleted')

175
app/routers_v3/results.py Normal file
View File

@ -0,0 +1,175 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, Result, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/results',
tags=['results']
)
class ResultModel(pydantic.BaseModel):
week: int
game: int
awayteam_id: int
hometeam_id: int
awayscore: int
homescore: int
season: int
scorecard_url: Optional[str] = None
class ResultList(pydantic.BaseModel):
results: List[ResultModel]
@router.get('')
async def get_results(
season: int, team_abbrev: list = Query(default=None), week_start: Optional[int] = None,
week_end: Optional[int] = None, game_num: list = Query(default=None),
away_abbrev: list = Query(default=None), home_abbrev: list = Query(default=None),
short_output: Optional[bool] = False):
all_results = Result.select_season(season)
if team_abbrev is not None:
team_list = []
for x in team_abbrev:
team_list.append(Team.get_season(x, season))
all_results = all_results.where(
(Result.awayteam << team_list) | (Result.hometeam << team_list)
)
if away_abbrev is not None:
team_list = []
for x in away_abbrev:
team_list.append(Team.get_season(x, season))
all_results = all_results.where(Result.awayteam << team_list)
if home_abbrev is not None:
team_list = []
for x in home_abbrev:
team_list.append(Team.get_season(x, season))
all_results = all_results.where(Result.hometeam << team_list)
if game_num is not None:
all_results = all_results.where(Result.game << game_num)
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)
return_results = {
'count': all_results.count(),
'results': [model_to_dict(x, recurse=not short_output) for x in all_results]
}
db.close()
return return_results
@router.get('/{result_id}')
async def get_one_result(result_id: int, short_output: Optional[bool] = False):
this_result = Result.get_or_none(Result.id == result_id)
if this_result is not None:
r_result = model_to_dict(this_result, recurse=not short_output)
else:
r_result = None
db.close()
return r_result
@router.patch('/{result_id}')
async def patch_result(
result_id: int, week_num: Optional[int] = None, game_num: Optional[int] = None,
away_team_id: Optional[int] = None, home_team_id: Optional[int] = None, away_score: Optional[int] = None,
home_score: Optional[int] = None, season: Optional[int] = None, scorecard_url: Optional[str] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_player - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_result = Result.get_or_none(Result.id == result_id)
if this_result is None:
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found')
if week_num is not None:
this_result.week = week_num
if game_num is not None:
this_result.game = game_num
if away_team_id is not None:
this_result.awayteam_id = away_team_id
if home_team_id is not None:
this_result.hometeam_id = home_team_id
if away_score is not None:
this_result.awayscore = away_score
if home_score is not None:
this_result.homescore = home_score
if season is not None:
this_result.season = season
if scorecard_url is not None:
this_result.scorecard_url = scorecard_url
if this_result.save() == 1:
r_result = model_to_dict(this_result)
db.close()
return r_result
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch result {result_id}')
@router.post('')
async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_player - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_results = []
for x in result_list.results:
if Team.get_or_none(Team.id == x.awayteam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found')
if Team.get_or_none(Team.id == x.hometeam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found')
new_results.append(x.dict())
with db.atomic():
for batch in chunked(new_results, 15):
Result.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_results)} results'
@router.delete('/{result_id}')
async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_result - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_result = Result.get_or_none(Result.id == result_id)
if not this_result:
db.close()
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found')
count = this_result.delete_instance()
db.close()
if count == 1:
return f'Result {result_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Result {result_id} could not be deleted')

157
app/routers_v3/schedules.py Normal file
View File

@ -0,0 +1,157 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, Schedule, Team, model_to_dict, chunked
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/schedules',
tags=['schedules']
)
class ScheduleModel(pydantic.BaseModel):
week: int
awayteam_id: int
hometeam_id: int
gamecount: int
season: int
class ScheduleList(pydantic.BaseModel):
schedules: List[ScheduleModel]
@router.get('')
async def get_schedules(
season: int, team_abbrev: list = Query(default=None), away_abbrev: list = Query(default=None),
home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None,
short_output: Optional[bool] = True):
all_sched = Schedule.select_season(season)
if team_abbrev is not None:
team_list = []
for x in team_abbrev:
team_list.append(Team.get_season(x, season))
all_sched = all_sched.where(
(Schedule.awayteam << team_list) | (Schedule.hometeam << team_list)
)
if away_abbrev is not None:
team_list = []
for x in away_abbrev:
team_list.append(Team.get_season(x, season))
all_sched = all_sched.where(Schedule.awayteam << team_list)
if home_abbrev is not None:
team_list = []
for x in home_abbrev:
team_list.append(Team.get_season(x, season))
all_sched = all_sched.where(Schedule.hometeam << team_list)
if week_start is not None:
all_sched = all_sched.where(Schedule.week >= week_start)
if week_end is not None:
all_sched = all_sched.where(Schedule.week <= week_end)
all_sched = all_sched.order_by(Schedule.id)
return_sched = {
'count': all_sched.count(),
'schedules': [model_to_dict(x, recurse=not short_output) for x in all_sched]
}
db.close()
return return_sched
@router.get('/{schedule_id}')
async def get_one_schedule(schedule_id: int):
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is not None:
r_sched = model_to_dict(this_sched)
else:
r_sched = None
db.close()
return r_sched
@router.patch('/{schedule_id}')
async def patch_schedule(
schedule_id: int, week: list = Query(default=None), awayteam_id: Optional[int] = None,
hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None,
token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_schedule - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is None:
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found')
if week is not None:
this_sched.week = week
if awayteam_id is not None:
this_sched.awayteam_id = awayteam_id
if hometeam_id is not None:
this_sched.hometeam_id = hometeam_id
if gamecount is not None:
this_sched.gamecount = gamecount
if season is not None:
this_sched.season = season
if this_sched.save() == 1:
r_sched = model_to_dict(this_sched)
db.close()
return r_sched
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch schedule {schedule_id}')
@router.post('')
async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_schedules - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_sched = []
for x in sched_list.schedules:
if Team.get_or_none(Team.id == x.awayteam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.awayteam_id} not found')
if Team.get_or_none(Team.id == x.hometeam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found')
new_sched.append(x.dict())
with db.atomic():
for batch in chunked(new_sched, 15):
Schedule.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_sched)} schedules'
@router.delete('/{schedule_id}')
async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_schedule - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is None:
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found')
count = this_sched.delete_instance()
db.close()
if count == 1:
return f'Schedule {this_sched} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Schedule {this_sched} could not be deleted')

View File

@ -0,0 +1,90 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from ..db_engine import db, Standings, Team, Division, model_to_dict, chunked, fn
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/standings',
tags=['standings']
)
@router.get('')
async def get_standings(
season: int, team_abbrev: list = Query(default=None), league_abbrev: Optional[str] = None,
division_abbrev: Optional[str] = None, short_output: Optional[bool] = False):
standings = Standings.select_season(season)
# if standings.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'No output for season {season}')
if team_abbrev is not None:
t_query = Team.select().where(fn.Lower(Team.abbrev) << [x.lower() for x in team_abbrev])
standings = standings.where(Standings.team << t_query)
if league_abbrev is not None:
l_query = Division.select().where(fn.Lower(Division.league_abbrev) == league_abbrev.lower())
standings = standings.where(Standings.team.division << l_query)
if division_abbrev is not None:
d_query = Division.select().where(fn.Lower(Division.division_abbrev) == division_abbrev.lower())
standings = standings.where(Standings.team.division << d_query)
def win_pct(this_team_stan):
if this_team_stan.wins + this_team_stan.losses == 0:
return 0
else:
return (this_team_stan.wins / (this_team_stan.wins + this_team_stan.losses)) + \
(this_team_stan.run_diff * .000001)
div_teams = [x for x in standings]
div_teams.sort(key=lambda team: win_pct(team), reverse=True)
return_standings = {
'count': len(div_teams),
'standings': [model_to_dict(x, recurse=not short_output) for x in div_teams]
}
db.close()
return return_standings
@router.patch('/{stan_id}')
async def patch_standings(
stan_id, wins: Optional[int] = None, losses: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_standings - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
try:
this_stan = Standings.get_by_id(stan_id)
except Exception as e:
db.close()
raise HTTPException(status_code=404, detail=f'No team found with id {stan_id}')
if wins:
this_stan.wins = wins
if losses:
this_stan.losses = losses
this_stan.save()
db.close()
return model_to_dict(this_stan)
@router.post('/s{season}/recalculate')
async def recalculate_standings(season: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'recalculate_standings - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
code = Standings.recalculate(season)
db.close()
if code == 69:
HTTPException(status_code=500, detail=f'Error recreating Standings rows')
raise HTTPException(status_code=200, detail=f'Just recalculated standings for season {season}')

249
app/routers_v3/teams.py Normal file
View File

@ -0,0 +1,249 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional, Literal
import copy
import logging
import pydantic
from ..db_engine import db, Team, Manager, Division, model_to_dict, chunked, fn
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/teams',
tags=['teams']
)
class TeamModel(pydantic.BaseModel):
abbrev: str
sname: str
lname: str
gmid: Optional[int] = None
gmid2: Optional[int] = None
manager1_id: Optional[int] = None
manager2_id: Optional[int] = None
division_id: Optional[int] = None
stadium: Optional[str] = None
thumbnail: Optional[str] = None
color: Optional[str] = None
dice_color: Optional[str] = None
season: int
class TeamList(pydantic.BaseModel):
teams: List[TeamModel]
@router.get('')
async def get_teams(
season: Optional[int] = None, owner_id: list = Query(default=None), manager_id: list = Query(default=None),
team_abbrev: list = Query(default=None), active_only: Optional[bool] = False,
short_output: Optional[bool] = False):
if season is not None:
all_teams = Team.select_season(season)
else:
all_teams = Team.select()
if manager_id is not None:
managers = Manager.select().where(Manager.id << manager_id)
all_teams = all_teams.where(
(Team.manager1_id << managers) | (Team.manager2_id << managers)
)
if owner_id:
all_teams = all_teams.where((Team.gmid << owner_id) | (Team.gmid2 << owner_id))
if team_abbrev is not None:
team_list = [x.lower() for x in team_abbrev]
all_teams = all_teams.where(fn.lower(Team.abbrev) << team_list)
if active_only:
all_teams = all_teams.where(
~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL'))
)
return_teams = {
'count': all_teams.count(),
'teams': [model_to_dict(x, recurse=not short_output) for x in all_teams]
}
db.close()
return return_teams
@router.get('/{team_id}')
async def get_one_team(team_id: int):
this_team = Team.get_or_none(Team.id == team_id)
if this_team:
r_team = model_to_dict(this_team)
else:
r_team = None
db.close()
return r_team
@router.get('/{team_id}/roster/{which}')
async def get_team_roster(team_id: int, which: Literal['current', 'next'], sort: Optional[str] = None):
try:
this_team = Team.get_by_id(team_id)
except Exception as e:
raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found')
if which == 'current':
full_roster = this_team.get_this_week()
else:
full_roster = this_team.get_next_week()
active_players = copy.deepcopy(full_roster['active']['players'])
sil_players = copy.deepcopy(full_roster['shortil']['players'])
lil_players = copy.deepcopy(full_roster['longil']['players'])
full_roster['active']['players'] = []
full_roster['shortil']['players'] = []
full_roster['longil']['players'] = []
for player in active_players:
full_roster['active']['players'].append(model_to_dict(player))
for player in sil_players:
full_roster['shortil']['players'].append(model_to_dict(player))
for player in lil_players:
full_roster['longil']['players'].append(model_to_dict(player))
if sort:
if sort == 'wara-desc':
full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True)
full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True)
full_roster['active']['players'].sort(key=lambda p: p["wara"], reverse=True)
db.close()
return full_roster
@router.patch('/{team_id}')
async def patch_team(
team_id: int, manager1_id: Optional[int] = None, manager2_id: Optional[int] = None, gmid: Optional[int] = None,
gmid2: Optional[int] = None, mascot: Optional[str] = None, stadium: Optional[str] = None,
thumbnail: Optional[str] = None, color: Optional[str] = None, abbrev: Optional[str] = None,
sname: Optional[str] = None, lname: Optional[str] = None, dice_color: Optional[str] = None,
division_id: Optional[int] = None, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'patch_team - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_team = Team.get_or_none(Team.id == team_id)
if not this_team:
return None
if abbrev is not None:
this_team.abbrev = abbrev
if manager1_id is not None:
if manager1_id == 0:
this_team.manager1 = None
else:
this_manager = Manager.get_or_none(Manager.id == manager1_id)
if not this_manager:
db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {manager1_id} not found')
this_team.manager1 = this_manager
if manager2_id is not None:
if manager2_id == 0:
this_team.manager2 = None
else:
this_manager = Manager.get_or_none(Manager.id == manager2_id)
if not this_manager:
db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {manager2_id} not found')
this_team.manager2 = this_manager
if gmid is not None:
this_team.gmid = gmid
if gmid2 is not None:
if gmid2 == 0:
this_team.gmid2 = None
else:
this_team.gmid2 = gmid2
if mascot is not None:
if mascot == 'False':
this_team.mascot = None
else:
this_team.mascot = mascot
if stadium is not None:
this_team.stadium = stadium
if thumbnail is not None:
this_team.thumbnail = thumbnail
if color is not None:
this_team.color = color
if dice_color is not None:
this_team.dice_color = dice_color
if sname is not None:
this_team.sname = sname
if lname is not None:
this_team.lname = lname
if division_id is not None:
if division_id == 0:
this_team.division = None
else:
this_division = Division.get_or_none(Division.id == division_id)
if not this_division:
db.close()
raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found')
this_team.division = this_division
if this_team.save():
r_team = model_to_dict(this_team)
db.close()
return r_team
else:
db.close()
raise HTTPException(status_code=500, detail=f'Unable to patch team {team_id}')
@router.post('')
async def post_team(team_list: TeamList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_team - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
new_teams = []
for team in team_list.teams:
dupe_team = Team.get_or_none(Team.season == team.season, Team.abbrev == team.abbrev)
if dupe_team:
db.close()
raise HTTPException(
status_code=500, detail=f'Team Abbrev {team.abbrev} already in use in Season {team.season}'
)
if team.manager1_id and not Manager.get_or_none(Manager.id == team.manager1_id):
db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {team.manager1_id} not found')
if team.manager2_id and not Manager.get_or_none(Manager.id == team.manager2_id):
db.close()
raise HTTPException(status_code=404, detail=f'Manager ID {team.manager2_id} not found')
if team.division_id and not Division.get_or_none(Division.id == team.division_id):
db.close()
raise HTTPException(status_code=404, detail=f'Division ID {team.division_id} not found')
new_teams.append(team.dict())
with db.atomic():
for batch in chunked(new_teams, 15):
Team.insert_many(batch).on_conflict_replace().execute()
db.close()
return f'Inserted {len(new_teams)} teams'
@router.get('/{team_id}')
async def delete_team(team_id: int, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_team - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
this_team = Team.get_or_none(Team.id == team_id)
if not this_team:
db.close()
raise HTTPException(status_code=404, detail=f'Team ID {team_id} not found')
count = this_team.delete_instance()
db.close()
if count == 1:
return f'Team {team_id} has been deleted'
else:
raise HTTPException(status_code=500, detail=f'Team {team_id} could not be deleted')

View File

@ -0,0 +1,153 @@
from fastapi import APIRouter, Depends, HTTPException, Query, Response
from typing import List, Optional
from pandas import DataFrame
import logging
import pydantic
from ..db_engine import db, Transaction, Team, Player, model_to_dict, chunked, fn
from ..dependencies import oauth2_scheme, valid_token
router = APIRouter(
prefix='/api/v3/transactions',
tags=['transactions']
)
class TransactionModel(pydantic.BaseModel):
week: int
player_id: int
oldteam_id: int
newteam_id: int
season: int
moveid: str
cancelled: Optional[bool] = False
frozen: Optional[bool] = False
class TransactionList(pydantic.BaseModel):
count: int
moves: List[TransactionModel]
@router.get('')
async def get_transactions(
season, team_abbrev: list = Query(default=None), week_start: Optional[int] = 0,
week_end: Optional[int] = None, cancelled: Optional[bool] = None, frozen: Optional[bool] = None,
player_name: list = Query(default=None), player_id: list = Query(default=None), move_id: Optional[str] = None,
is_trade: Optional[bool] = None, short_output: Optional[bool] = False):
if season:
transactions = Transaction.select_season(season)
else:
transactions = Transaction.select()
if team_abbrev is not None:
t_list = [x.upper() for x in team_abbrev]
these_teams = Team.select().where((Team.abbrev << t_list))
transactions = transactions.where(
(Transaction.newteam << these_teams) | (Transaction.oldteam << these_teams)
)
if week_start is not None:
transactions = transactions.where(Transaction.week >= week_start)
if week_end is not None:
transactions = transactions.where(Transaction.week <= week_end)
if move_id:
transactions = transactions.where(Transaction.moveid == move_id)
if player_id or player_name:
if player_id:
p_list = Player.select().where(Player.id << player_id)
transactions = transactions.where(Transaction.player << p_list)
else:
p_list = [x.lower() for x in player_name]
these_players = Player.select().where(fn.Lower(Player.name) << p_list)
transactions = transactions.where(Transaction.player << these_players)
if cancelled:
transactions = transactions.where(Transaction.cancelled == 1)
else:
transactions = transactions.where(Transaction.cancelled == 0)
if frozen:
transactions = transactions.where(Transaction.frozen == 1)
else:
transactions = transactions.where(Transaction.frozen == 0)
if is_trade is not None:
raise HTTPException(status_code=501, detail='The is_trade parameter is not implemented, yet')
transactions = transactions.order_by(-Transaction.week, Transaction.moveid)
return_trans = {
'count': transactions.count(),
'transactions': [model_to_dict(x, recurse=not short_output) for x in transactions]
}
db.close()
return return_trans
@router.patch('/{move_id}')
async def patch_transactions(
move_id, token: str = Depends(oauth2_scheme), frozen: Optional[bool] = None, cancelled: Optional[bool] = None):
if not valid_token(token):
logging.warning(f'patch_transactions - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
these_moves = Transaction.select().where(Transaction.moveid == move_id)
if these_moves.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'Move ID {move_id} not found')
if frozen is not None:
for x in these_moves:
x.frozen = frozen
x.save()
if cancelled is not None:
for x in these_moves:
x.cancelled = cancelled
x.save()
db.close()
raise HTTPException(status_code=200, detail=f'Updated {these_moves.count()} transactions')
@router.post('')
async def post_transactions(moves: TransactionList, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'post_transactions - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
all_moves = []
for x in moves.moves:
if Team.get_or_none(Team.id == x.oldteam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.oldteam_id} not found')
if Team.get_or_none(Team.id == x.newteam_id) is None:
raise HTTPException(status_code=404, detail=f'Team ID {x.newteam_id} not found')
if Player.get_or_none(Player.id == x.player_id) is None:
raise HTTPException(status_code=404, detail=f'Player ID {x.player_id} not found')
all_moves.append(x.dict())
with db.atomic():
for batch in chunked(all_moves, 15):
Transaction.insert_many(batch).on_conflict_replace().execute()
db.close()
raise HTTPException(status_code=200, detail=f'{len(all_moves)} transactions have been added')
@router.delete('/{move_id}')
async def delete_transactions(move_id, token: str = Depends(oauth2_scheme)):
if not valid_token(token):
logging.warning(f'delete_transactions - Bad Token: {token}')
raise HTTPException(status_code=401, detail='Unauthorized')
delete_query = Transaction.delete().where(Transaction.moveid == move_id)
count = delete_query.execute()
db.close()
if count > 0:
raise HTTPException(status_code=200, detail=f'Removed {count} transactions')
else:
raise HTTPException(status_code=418, detail=f'Well slap my ass and call me a teapot; '
f'I did not delete any records')

View File

@ -1539,7 +1539,7 @@ async def v1_players_patch(
raise HTTPException(status_code=404, detail=f'Team id {team_id} not found')
if name:
this_player.name = name
if wara:
if wara is not None:
this_player.wara = wara
if image:
this_player.image = image

View File

@ -1,5 +1,5 @@
fastapi==0.61.1
uvicorn==0.12.2
fastapi
uvicorn
peewee==3.13.3
python-multipart
pandas