From 1d652ee5fa50da5f3022a9267cf92d1047682057 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Fri, 30 May 2025 21:41:40 -0500 Subject: [PATCH] Added SbaPlayer table --- app/db_engine.py | 10 ++ app/main.py | 3 +- app/routers_v3/players.py | 9 +- app/routers_v3/sbaplayers.py | 248 +++++++++++++++++++++++++++++++++++ 4 files changed, 266 insertions(+), 4 deletions(-) create mode 100644 app/routers_v3/sbaplayers.py diff --git a/app/db_engine.py b/app/db_engine.py index 020f858..73a8431 100644 --- a/app/db_engine.py +++ b/app/db_engine.py @@ -820,6 +820,15 @@ class Result(BaseModel): # away_stan.save() +class SbaPlayer(BaseModel): + first_name = CharField() + last_name = CharField() + key_fangraphs = IntegerField(null=True) + key_bbref = CharField(null=True) + key_retro = CharField(null=True) + key_mlbam = IntegerField(null=True) + + class Player(BaseModel): name = CharField() wara = FloatField() @@ -845,6 +854,7 @@ class Player(BaseModel): strat_code = CharField(null=True) bbref_id = CharField(null=True) injury_rating = CharField(null=True) + sbaplayer_id = ForeignKeyField(SbaPlayer, null=True) @staticmethod def select_season(num): diff --git a/app/main.py b/app/main.py index accc180..2b58752 100644 --- a/app/main.py +++ b/app/main.py @@ -12,7 +12,7 @@ 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, stratgame, stratplay, \ - injuries, decisions, divisions + injuries, decisions, divisions, sbaplayers # 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 logging.WARNING @@ -67,6 +67,7 @@ app.include_router(stratplay.router) app.include_router(injuries.router) app.include_router(decisions.router) app.include_router(divisions.router) +app.include_router(sbaplayers.router) logger.info(f'Loaded all routers.') diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index 4db5a65..89cc885 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -40,6 +40,7 @@ class PlayerModel(pydantic.BaseModel): strat_code: Optional[str] = None bbref_id: Optional[str] = None injury_rating: Optional[str] = None + sbaplayer_id: Optional[int] = None class PlayerList(pydantic.BaseModel): @@ -87,7 +88,7 @@ async def get_players( 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'] + 'headshot', 'vanity_card', 'strat_code', 'bbref_id', 'injury_rating', 'player_id', 'sbaref_id'] ] for line in all_players: player_list.append( @@ -96,7 +97,7 @@ async def get_players( 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(",", "-_-") if line.strat_code is not None else "", - line.bbref_id, line.injury_rating, line.id + line.bbref_id, line.injury_rating, line.id, line.sbaplayer_id ] ) return_players = { @@ -156,7 +157,7 @@ async def patch_player( 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, 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): + bbref_id: Optional[str] = None, injury_rating: Optional[str] = None, sbaref_id: Optional[int] = None): if not valid_token(token): logger.warning(f'patch_player - Bad Token: {token}') raise HTTPException(status_code=401, detail='Unauthorized') @@ -217,6 +218,8 @@ async def patch_player( this_player.bbref_id = bbref_id if injury_rating is not None: this_player.injury_rating = injury_rating + if sbaref_id is not None: + this_player.sbaplayer_id = sbaref_id if this_player.save() == 1: r_player = model_to_dict(this_player) diff --git a/app/routers_v3/sbaplayers.py b/app/routers_v3/sbaplayers.py new file mode 100644 index 0000000..b599c83 --- /dev/null +++ b/app/routers_v3/sbaplayers.py @@ -0,0 +1,248 @@ +import datetime +import random + +from fastapi import APIRouter, Depends, HTTPException, Response, Query +from typing import Optional, List +import logging +import pydantic + +from ..db_engine import db, SbaPlayer, model_to_dict, fn, chunked, query_to_csv +from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA + +logger = logging.getLogger('discord_app') + +router = APIRouter( + prefix='/api/v3/sbaplayers', + tags=['sbaplayers'] +) + + +class SbaPlayerModel(pydantic.BaseModel): + first_name: str + last_name: str + key_mlbam: int = None + key_fangraphs: int = None + key_bbref: str = None + key_retro: str = None + + +class PlayerList(pydantic.BaseModel): + players: List[SbaPlayerModel] + + +@router.get('') +async def get_players( + full_name: list = Query(default=None), first_name: list = Query(default=None), + last_name: list = Query(default=None), key_fangraphs: list = Query(default=None), + key_bbref: list = Query(default=None), key_retro: list = Query(default=None), + key_mlbam: list = Query(default=None), sort: Optional[str] = None, csv: Optional[bool] = False): + all_players = SbaPlayer.select() + + if full_name is not None: + name_list = [x.lower() for x in full_name] + all_players = all_players.where( + fn.lower(SbaPlayer.first_name) + ' ' + fn.lower(SbaPlayer.last_name) << name_list + ) + if first_name is not None: + name_list = [x.lower() for x in first_name] + all_players = all_players.where(fn.lower(SbaPlayer.first_name) << name_list) + if last_name is not None: + name_list = [x.lower() for x in last_name] + all_players = all_players.where(fn.lower(SbaPlayer.last_name) << name_list) + if key_fangraphs is not None: + all_players = all_players.where(SbaPlayer.key_fangraphs << key_fangraphs) + if key_bbref is not None: + name_list = [x.lower() for x in key_bbref] + all_players = all_players.where(fn.lower(SbaPlayer.key_bbref) << name_list) + if key_retro is not None: + name_list = [x.lower() for x in key_retro] + all_players = all_players.where(fn.lower(SbaPlayer.key_retro) << name_list) + if key_mlbam is not None: + all_players = all_players.where(SbaPlayer.key_mlbam << key_mlbam) + + if sort is not None: + if sort in ['firstname-asc', 'fullname-asc']: + all_players = all_players.order_by(SbaPlayer.first_name) + elif sort in ['firstname-desc', 'fullname-desc']: + all_players = all_players.order_by(-SbaPlayer.first_name) + elif sort == 'lastname-asc': + all_players = all_players.order_by(SbaPlayer.last_name) + elif sort == 'lastname-desc': + all_players = all_players.order_by(-SbaPlayer.last_name) + elif sort == 'fangraphs-asc': + all_players = all_players.order_by(SbaPlayer.key_fangraphs) + elif sort == 'fangraphs-desc': + all_players = all_players.order_by(-SbaPlayer.key_fangraphs) + elif sort == 'bbref-asc': + all_players = all_players.order_by(SbaPlayer.key_bbref) + elif sort == 'bbref-desc': + all_players = all_players.order_by(-SbaPlayer.key_bbref) + elif sort == 'retro-asc': + all_players = all_players.order_by(SbaPlayer.key_retro) + elif sort == 'retro-desc': + all_players = all_players.order_by(-SbaPlayer.key_retro) + elif sort == 'mlbam-asc': + all_players = all_players.order_by(SbaPlayer.key_mlbam) + elif sort == 'mlbam-desc': + all_players = all_players.order_by(-SbaPlayer.key_mlbam) + + if csv: + return_val = query_to_csv(all_players) + db.close() + return Response(content=return_val, media_type='text/csv') + + return_val = {'count': all_players.count(), 'players': [ + model_to_dict(x) for x in all_players + ]} + db.close() + return return_val + + +@router.get('/{player_id}') +async def get_one_player(player_id: int): + this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) + if this_player is None: + db.close() + raise HTTPException(status_code=404, detail=f'SbaPlayer id {player_id} not found') + + r_data = model_to_dict(this_player) + db.close() + return r_data + + +@router.patch('/{player_id}', include_in_schema=PRIVATE_IN_SCHEMA) +async def patch_player( + player_id: int, first_name: Optional[str] = None, last_name: Optional[str] = None, + key_fangraphs: Optional[str] = None, key_bbref: Optional[str] = None, key_retro: Optional[str] = None, + key_mlbam: Optional[str] = None, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'Bad Token: {token}') + db.close() + raise HTTPException( + status_code=401, + detail='You are not authorized to patch mlb players. This event has been logged.' + ) + + this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) + if this_player is None: + db.close() + raise HTTPException(status_code=404, detail=f'SbaPlayer id {player_id} not found') + + if first_name is not None: + this_player.first_name = first_name + if last_name is not None: + this_player.last_name = last_name + if key_fangraphs is not None: + this_player.key_fangraphs = key_fangraphs + if key_bbref is not None: + this_player.key_bbref = key_bbref + if key_retro is not None: + this_player.key_retro = key_retro + if key_mlbam is not None: + this_player.key_mlbam = key_mlbam + + if this_player.save() == 1: + return_val = model_to_dict(this_player) + db.close() + return return_val + else: + db.close() + raise HTTPException( + status_code=418, + detail='Well slap my ass and call me a teapot; I could not save that player' + ) + + +@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'Bad Token: {token}') + db.close() + raise HTTPException( + status_code=401, + detail='You are not authorized to post mlb players. This event has been logged.' + ) + + new_players = [] + for x in players.players: + dupes = SbaPlayer.select().where( + (SbaPlayer.key_fangraphs == x.key_fangraphs) | (SbaPlayer.key_mlbam == x.key_mlbam) | + (SbaPlayer.key_retro == x.key_retro) | (SbaPlayer.key_bbref == x.key_bbref) + ) + if dupes.count() > 0: + db.close() + raise HTTPException( + status_code=400, + detail=f'{x.first_name} {x.last_name} has a key already in the database' + ) + + new_players.append(x.dict()) + + with db.atomic(): + for batch in chunked(new_players, 15): + SbaPlayer.insert_many(batch).on_conflict_replace().execute() + db.close() + + return f'Inserted {len(new_players)} new MLB players' + + +@router.post('/one', include_in_schema=PRIVATE_IN_SCHEMA) +async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'Bad Token: {token}') + db.close() + raise HTTPException( + status_code=401, + detail='You are not authorized to post mlb players. This event has been logged.' + ) + + dupes = SbaPlayer.select().where( + (SbaPlayer.key_fangraphs == player.key_fangraphs) | (SbaPlayer.key_mlbam == player.key_mlbam) | + (SbaPlayer.key_bbref == player.key_bbref) + ) + if dupes.count() > 0: + logging.info(f'POST /SbaPlayers/one - dupes found:') + for x in dupes: + logging.info(f'{x}') + db.close() + raise HTTPException( + status_code=400, + detail=f'{player.first_name} {player.last_name} has a key already in the database' + ) + + new_player = SbaPlayer(**player.dict()) + saved = new_player.save() + if saved == 1: + return_val = model_to_dict(new_player) + db.close() + return return_val + else: + db.close() + raise HTTPException( + status_code=418, + detail='Well slap my ass and call me a teapot; I could not save that player' + ) + + +@router.delete('/{player_id}', include_in_schema=PRIVATE_IN_SCHEMA) +async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): + if not valid_token(token): + logging.warning(f'Bad Token: {token}') + db.close() + raise HTTPException( + status_code=401, + detail='You are not authorized to delete mlb players. This event has been logged.' + ) + + this_player = SbaPlayer.get_or_none(SbaPlayer.id == player_id) + if this_player is None: + db.close() + raise HTTPException(status_code=404, detail=f'SbaPlayer id {player_id} not found') + + count = this_player.delete_instance() + db.close() + + if count == 1: + raise HTTPException(status_code=200, detail=f'Player {player_id} has been deleted') + else: + raise HTTPException(status_code=500, detail=f'Player {player_id} was not deleted') \ No newline at end of file