/standings, /teams, /transactions added

This commit is contained in:
Cal Corum 2023-03-22 15:56:10 -05:00
parent 0c7712cd78
commit 54b0485599
7 changed files with 524 additions and 28 deletions

View File

@ -1,6 +1,6 @@
from fastapi import Depends, FastAPI
from routers_v3 import current, players, results, schedules
from routers_v3 import current, players, results, schedules, standings, teams, transactions
app = FastAPI(
responses={404: {'description': 'Not found'}}
@ -11,6 +11,9 @@ 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.get("/api")

View File

@ -44,7 +44,7 @@ class PlayerList(pydantic.BaseModel):
@router.get('')
def get_players(
season: Optional[int], team_id: list = Query(default=None), pos: list = Query(default=None),
is_injured: Optional[bool] = None, sort: Optional[str] = None):
is_injured: Optional[bool] = None, short_output: Optional[bool] = False, sort: Optional[str] = None):
logging.info(f'team_id: {team_id}')
all_players = Player.select_season(season)
@ -74,17 +74,17 @@ def get_players(
return_players = {
'count': all_players.count(),
'players': [model_to_dict(x) for x in all_players]
'players': [model_to_dict(x, recurse=not short_output) for x in all_players]
}
db.close()
return return_players
@router.get('/{player_id}')
def get_one_player(player_id: int):
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)
r_player = model_to_dict(this_player, recurse=not short_output)
else:
r_player = None
db.close()

View File

@ -31,7 +31,8 @@ class ResultList(pydantic.BaseModel):
def get_results(
season: int, team_abbrev: list = Query(default=None), week_start: list = Query(default=None),
week_end: list = Query(default=None), game_num: list = Query(default=None),
away_abbrev: list = Query(default=None), home_abbrev: 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:
@ -65,17 +66,17 @@ def get_results(
return_results = {
'count': all_results.count(),
'results': [model_to_dict(x) for x in all_results]
'results': [model_to_dict(x, recurse=not short_output) for x in all_results]
}
db.close()
return return_results
@router.get('/{result_id}')
def get_one_result(result_id: int):
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)
r_result = model_to_dict(this_result, recurse=not short_output)
else:
r_result = None
db.close()
@ -86,10 +87,15 @@ def get_one_result(result_id: int):
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):
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 KeyError(f'Result ID {result_id} not found')
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found')
if week_num is not None:
this_result.week = week_num
@ -121,17 +127,21 @@ def patch_result(
return r_result
else:
db.close()
raise DatabaseError(f'Unable to patch result {result_id}')
raise HTTPException(status_code=500, detail=f'Unable to patch result {result_id}')
@router.post('')
def post_results(result_list: ResultList):
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 KeyError(f'Team ID {x.awayteam_id} not found')
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 KeyError(f'Team ID {x.hometeam_id} not found')
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found')
new_results.append(x.dict())
@ -144,11 +154,15 @@ def post_results(result_list: ResultList):
@router.delete('/{result_id}')
def delete_result(result_id: int):
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 KeyError(f'Result ID {result_id} not found')
raise HTTPException(status_code=404, detail=f'Result ID {result_id} not found')
count = this_result.delete_instance()
db.close()
@ -156,6 +170,6 @@ def delete_result(result_id: int):
if count == 1:
return f'Result {result_id} has been deleted'
else:
raise DatabaseError(f'Result {result_id} could not be deleted')
raise HTTPException(status_code=500, detail=f'Result {result_id} could not be deleted')

View File

@ -27,7 +27,8 @@ class ScheduleList(pydantic.BaseModel):
@router.get('')
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):
home_abbrev: list = Query(default=None), week_start: Optional[int] = None, week_end: Optional[int] = None,
short_output: Optional[bool] = False):
all_sched = Schedule.select_season(season)
if team_abbrev is not None:
@ -60,17 +61,17 @@ def get_schedules(
return_sched = {
'count': all_sched.count(),
'schedules': [model_to_dict(x) for x in all_sched]
'schedules': [model_to_dict(x, recurse=not short_output) for x in all_sched]
}
db.close()
return return_sched
@router.get('/{schedule_id}')
def get_one_schedule(schedule_id: int):
def get_one_schedule(schedule_id: int, short_output: Optional[bool] = False):
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is not None:
r_sched = model_to_dict(this_sched)
r_sched = model_to_dict(this_sched, recurse=not short_output)
else:
r_sched = None
db.close()
@ -83,7 +84,7 @@ def patch_schedule(
hometeam_id: Optional[int] = None, gamecount: Optional[int] = None, season: Optional[int] = None):
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is None:
raise KeyError(f'Schedule ID {schedule_id} not found')
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found')
if week is not None:
this_sched.week = week
@ -106,7 +107,7 @@ def patch_schedule(
return r_sched
else:
db.close()
raise DatabaseError(f'Unable to patch schedule {schedule_id}')
raise HTTPException(status_code=500, detail=f'Unable to patch schedule {schedule_id}')
@router.post('')
@ -114,9 +115,9 @@ def post_schedules(sched_list: ScheduleList):
new_sched = []
for x in sched_list.schedules:
if Team.get_or_none(Team.id == x.awayteam_id) is None:
raise KeyError(f'Team ID {x.awayteam_id} not found')
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 KeyError(f'Team ID {x.hometeam_id} not found')
raise HTTPException(status_code=404, detail=f'Team ID {x.hometeam_id} not found')
new_sched.append(x.dict())
@ -132,7 +133,7 @@ def post_schedules(sched_list: ScheduleList):
def delete_schedule(schedule_id: int):
this_sched = Schedule.get_or_none(Schedule.id == schedule_id)
if this_sched is None:
raise KeyError(f'Schedule ID {schedule_id} not found')
raise HTTPException(status_code=404, detail=f'Schedule ID {schedule_id} not found')
count = this_sched.delete_instance()
db.close()
@ -140,4 +141,4 @@ def delete_schedule(schedule_id: int):
if count == 1:
return f'Schedule {this_sched} has been deleted'
else:
raise DatabaseError(f'Schedule {this_sched} could not be deleted')
raise HTTPException(status_code=500, detail=f'Schedule {this_sched} could not be deleted')

102
app/routers_v3/standings.py Normal file
View File

@ -0,0 +1,102 @@
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, logging
router = APIRouter(
prefix='/api/v3/standings',
tags=['standings']
)
@router.get('')
async def v1_standings(
season: int, team_abbrev: Optional[str] = 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:
this_team = Team.get_season(team_abbrev, season)
if not this_team:
db.close()
raise HTTPException(status_code=404, detail=f'Team {team_abbrev} not found')
standings = standings.where(Standings.team == this_team)
if league_abbrev:
these_divisions = Division.select().where(fn.Lower(Division.league_abbrev) == league_abbrev.lower())
if these_divisions.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'No output for league {league_abbrev}')
standings = standings.where(Standings.team.division << these_divisions)
if division_abbrev:
this_division = Division.select().where(fn.Lower(Division.division_abbrev) == division_abbrev.lower())
if not this_division:
db.close()
raise HTTPException(status_code=404, detail=f'No output for division {division_abbrev}')
standings = standings.where(Standings.team.division << this_division)
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 = [team_stan for team_stan 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('/api/v1/standings/{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_player - 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('/api/v1/standings/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}')

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

@ -0,0 +1,200 @@
from fastapi import APIRouter, Depends, HTTPException, Query
from typing import List, Optional
import logging
import pydantic
from db_engine import db, Team, Manager, Division, model_to_dict, chunked
from dependencies import oauth2_scheme, valid_token, logging
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('')
def get_teams(
season: Optional[int] = None, owner_id: Optional[int] = None, manager_id: Optional[int] = None,
abbrev: Optional[str] = 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:
all_teams = all_teams.where(
(Team.manager1_id == manager_id) | (Team.manager2_id == manager_id)
)
if owner_id:
all_teams = all_teams.where((Team.gmid == owner_id) | (Team.gmid2 == owner_id))
if active_only:
all_teams = all_teams.where(
~(Team.abbrev.endswith('IL')) & ~(Team.abbrev.endswith('MiL'))
)
if abbrev is not None:
all_teams = all_teams.where(Team.abbrev == abbrev)
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}')
def get_one_team(team_id: int, short_output: Optional[bool] = False):
this_team = Team.get_or_none(Team.id == team_id)
if this_team:
r_team = model_to_dict(this_team, recurse=not short_output)
else:
r_team = None
db.close()
return r_team
@router.get('/{team_id}')
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):
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.get('')
def post_team(team_list: TeamList):
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}')
def delete_team(team_id: int):
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,176 @@
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, logging
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: Optional[str] = None, week_start: Optional[int] = 0,
week_end: Optional[int] = None, cancelled: Optional[bool] = None, frozen: Optional[bool] = None,
player_name: Optional[str] = None, player_id: Optional[int] = 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 transactions.count() == 0:
# db.close()
# raise HTTPException(status_code=404, detail=f'Season {season} not found')
if team_abbrev:
these_teams = Team.select().where(
(Team.abbrev == team_abbrev.upper()) |
(Team.abbrev == f'{team_abbrev.upper()}MiL') | (Team.abbrev == f'{team_abbrev.upper()}IL')
)
if these_teams.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'Team {team_abbrev} not found')
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:
try:
this_player = Player.get_by_id(player_id)
except Exception as e:
db.close()
raise HTTPException(status_code=404, detail=f'Player id {player_id} not found')
transactions = transactions.where(Transaction.player == this_player)
else:
these_players = Player.select().where(fn.Lower(Player.name) == player_name.lower())
print(f'these_players: {these_players}\nCount: {these_players.count()}')
if these_players.count() == 0:
db.close()
raise HTTPException(status_code=404, detail=f'Player {player_name} not found')
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):
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')