From 9ec69f9f2cc392d26882e4f2c4d40812618e9220 Mon Sep 17 00:00:00 2001 From: Cal Corum Date: Mon, 9 Mar 2026 19:34:28 -0500 Subject: [PATCH] fix: standardize all collection POST routes to use trailing slash aiohttp follows 307 redirects but converts POST to GET, silently dropping the request body. Standardize all @router.post('') to @router.post('/') so the canonical URL always has a trailing slash, preventing 307 redirects when clients POST with trailing slashes. Co-Authored-By: Claude Opus 4.6 --- app/routers_v3/awards.py | 120 +++-- app/routers_v3/battingstats.py | 311 ++++++++----- app/routers_v3/current.py | 73 +-- app/routers_v3/custom_commands.py | 732 ++++++++++++++++-------------- app/routers_v3/decisions.py | 146 ++++-- app/routers_v3/divisions.py | 89 ++-- app/routers_v3/draftlist.py | 71 +-- app/routers_v3/draftpicks.py | 111 +++-- app/routers_v3/help_commands.py | 237 +++++----- app/routers_v3/injuries.py | 85 ++-- app/routers_v3/keepers.py | 73 +-- app/routers_v3/managers.py | 105 +++-- app/routers_v3/pitchingstats.py | 276 ++++++----- app/routers_v3/players.py | 72 +-- app/routers_v3/results.py | 96 ++-- app/routers_v3/sbaplayers.py | 156 ++++--- app/routers_v3/schedules.py | 92 ++-- app/routers_v3/stratgame.py | 2 +- app/routers_v3/teams.py | 80 ++-- app/routers_v3/transactions.py | 2 +- 20 files changed, 1717 insertions(+), 1212 deletions(-) diff --git a/app/routers_v3/awards.py b/app/routers_v3/awards.py index 7641c7f..575ef42 100644 --- a/app/routers_v3/awards.py +++ b/app/routers_v3/awards.py @@ -4,15 +4,17 @@ 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, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/awards', - tags=['awards'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/awards", tags=["awards"]) + class AwardModel(pydantic.BaseModel): name: str @@ -30,13 +32,18 @@ class AwardList(pydantic.BaseModel): awards: List[AwardModel] -@router.get('') +@router.get("") @handle_db_errors 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, - player_name: list = Query(default=None)): + 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, + player_name: list = Query(default=None), +): all_awards = Award.select() if name is not None: @@ -61,39 +68,47 @@ async def get_awards( all_awards = all_awards.where(Award.player << all_players) return_awards = { - 'count': all_awards.count(), - 'awards': [model_to_dict(x, recurse=not short_output) for x in all_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}') +@router.get("/{award_id}") @handle_db_errors 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') + 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}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{award_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors 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)): + 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): - logger.warning(f'patch_player - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + raise HTTPException(status_code=404, detail=f"Award ID {award_id} not found") if name is not None: this_award.name = name @@ -118,26 +133,43 @@ async def patch_award( return r_award else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch award {award_id}') + raise HTTPException(status_code=500, detail=f"Unable to patch award {award_id}") -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'patch_player - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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.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') + raise HTTPException( + status_code=404, detail=f"Team ID {x.team_id} not found" + ) new_awards.append(x.dict()) @@ -146,29 +178,27 @@ async def post_award(award_list: AwardList, token: str = Depends(oauth2_scheme)) Award.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_awards)} awards' + return f"Inserted {len(new_awards)} awards" -@router.delete('/{award_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{award_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_award(award_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'patch_player - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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' + return f"Award {award_id} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Award {award_id} could not be deleted') - - - - + raise HTTPException( + status_code=500, detail=f"Award {award_id} could not be deleted" + ) diff --git a/app/routers_v3/battingstats.py b/app/routers_v3/battingstats.py index 7471cf9..68df4d5 100644 --- a/app/routers_v3/battingstats.py +++ b/app/routers_v3/battingstats.py @@ -3,15 +3,27 @@ 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, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/battingstats', - tags=['battingstats'] +from ..db_engine import ( + db, + BattingStat, + Team, + Player, + Current, + model_to_dict, + chunked, + fn, + per_season_weeks, ) +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, +) + +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/battingstats", tags=["battingstats"]) class BatStatModel(pydantic.BaseModel): @@ -60,29 +72,37 @@ class BatStatList(pydantic.BaseModel): stats: List[BatStatModel] -@router.get('') +@router.get("") @handle_db_errors 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(): + 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']: + 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': []} + return {"count": 0, "stats": []} else: all_stats = BattingStat.regular_season(season) if all_stats.count() == 0: db.close() - return {'count': 0, 'stats': []} + return {"count": 0, "stats": []} if position is not None: all_stats = all_stats.where(BattingStat.pos << [x.upper() for x in position]) @@ -93,7 +113,9 @@ async def get_batstats( 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]) + 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) @@ -108,21 +130,19 @@ async def get_batstats( db.close() raise HTTPException( status_code=404, - detail=f'Start week {start} is after end week {end} - cannot pull stats' + detail=f"Start week {start} is after end week {end} - cannot pull stats", ) - all_stats = all_stats.where( - (BattingStat.week >= start) & (BattingStat.week <= end) - ) + all_stats = all_stats.where((BattingStat.week >= start) & (BattingStat.week <= end)) if limit: all_stats = all_stats.limit(limit) if sort: - if sort == 'newest': + 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], + "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] } @@ -130,52 +150,82 @@ async def get_batstats( return return_stats -@router.get('/totals') +@router.get("/totals") @handle_db_errors 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)): + 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.') + raise HTTPException( + status_code=400, + detail=f"Only one of s_type, week_start/week_end, or week may be used.", + ) # Build SELECT fields conditionally based on group_by to match GROUP BY exactly select_fields = [] - - if group_by == 'player': + + if group_by == "player": select_fields = [BattingStat.player] - elif group_by == 'team': + elif group_by == "team": select_fields = [BattingStat.team] - elif group_by == 'playerteam': + elif group_by == "playerteam": select_fields = [BattingStat.player, BattingStat.team] else: # Default case select_fields = [BattingStat.player] all_stats = ( - BattingStat - .select(*select_fields, - 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')) - .where(BattingStat.season == season) - .having(fn.SUM(BattingStat.pa) >= min_pa) + BattingStat.select( + *select_fields, + 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"), + ) + .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]: @@ -185,16 +235,20 @@ async def get_totalstats( 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.' + 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", ) - 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 + weeks["end"] = week_end all_stats = all_stats.where( - (BattingStat.week >= weeks['start']) & (BattingStat.week <= weeks['end']) + (BattingStat.week >= weeks["start"]) & (BattingStat.week <= weeks["end"]) ) elif week is not None: all_stats = all_stats.where(BattingStat.week << week) @@ -204,14 +258,20 @@ async def get_totalstats( 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) + (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': + if sort == "player": all_stats = all_stats.order_by(BattingStat.player) - elif sort == 'team': + elif sort == "team": all_stats = all_stats.order_by(BattingStat.team) if group_by is not None: # Use the same fields for GROUP BY as we used for SELECT @@ -227,56 +287,63 @@ async def get_totalstats( 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_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_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': [] - } - + return_stats = {"count": all_stats.count(), "stats": []} + for x in all_stats: # Handle player field based on grouping with safe access - this_player = 'TOT' - if 'player' in group_by and hasattr(x, 'player'): - this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False) + this_player = "TOT" + if "player" in group_by and hasattr(x, "player"): + this_player = ( + x.player_id if short_output else model_to_dict(x.player, recurse=False) + ) - # Handle team field based on grouping with safe access - this_team = 'TOT' - if 'team' in group_by and hasattr(x, 'team'): - this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False) - - return_stats['stats'].append({ - 'player': this_player, - 'team': this_team, - '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 - }) + # Handle team field based on grouping with safe access + this_team = "TOT" + if "team" in group_by and hasattr(x, "team"): + this_team = ( + x.team_id if short_output else model_to_dict(x.team, recurse=False) + ) + + return_stats["stats"].append( + { + "player": this_player, + "team": this_team, + "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, + } + ) db.close() return return_stats @@ -287,15 +354,17 @@ async def get_totalstats( # pass # Keep Career Stats table and recalculate after posting stats -@router.patch('/{stat_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{stat_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme)): +async def patch_batstats( + stat_id: int, new_stats: BatStatModel, token: str = Depends(oauth2_scheme) +): if not valid_token(token): - logger.warning(f'patch_batstats - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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)) @@ -303,12 +372,12 @@ async def patch_batstats(stat_id: int, new_stats: BatStatModel, token: str = Dep return r_stat -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_batstats - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_batstats - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") all_stats = [] @@ -316,9 +385,13 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme) 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') + 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') + raise HTTPException( + status_code=404, detail=f"Player ID {x.player_id} not found" + ) all_stats.append(BattingStat(**x.dict())) @@ -329,4 +402,4 @@ async def post_batstats(s_list: BatStatList, token: str = Depends(oauth2_scheme) # Update career stats db.close() - return f'Added {len(all_stats)} batting lines' + return f"Added {len(all_stats)} batting lines" diff --git a/app/routers_v3/current.py b/app/routers_v3/current.py index 0db0aaa..ba4458f 100644 --- a/app/routers_v3/current.py +++ b/app/routers_v3/current.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, Current, model_to_dict -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/current', - tags=['current'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/current", tags=["current"]) + class CurrentModel(pydantic.BaseModel): week: Optional[int] = 0 @@ -29,7 +31,7 @@ class CurrentModel(pydantic.BaseModel): injury_count: Optional[int] = 0 -@router.get('') +@router.get("") @handle_db_errors async def get_current(season: Optional[int] = None): if season is not None: @@ -45,21 +47,33 @@ async def get_current(season: Optional[int] = None): return None -@router.patch('/{current_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{current_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors 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)): + 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): - logger.warning(f'patch_current - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + raise HTTPException( + status_code=404, detail=f"Current id {current_id} not found" + ) if week is not None: current.week = week @@ -90,15 +104,17 @@ async def patch_current( return r_curr else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch current {current_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch current {current_id}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'patch_current - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"patch_current - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_current = Current(**new_current.dict()) @@ -108,17 +124,22 @@ async def post_current(new_current: CurrentModel, token: str = Depends(oauth2_sc return r_curr else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to post season {new_current.season} current') + raise HTTPException( + status_code=500, + detail=f"Unable to post season {new_current.season} current", + ) -@router.delete('/{current_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{current_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_current(current_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'patch_current - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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}' + return f"Deleted current ID {current_id}" - raise HTTPException(status_code=500, detail=f'Unable to delete current {current_id}') + raise HTTPException( + status_code=500, detail=f"Unable to delete current {current_id}" + ) diff --git a/app/routers_v3/custom_commands.py b/app/routers_v3/custom_commands.py index 4830a50..bbe3077 100644 --- a/app/routers_v3/custom_commands.py +++ b/app/routers_v3/custom_commands.py @@ -7,15 +7,17 @@ import json from playhouse.shortcuts import model_to_dict from peewee import fn -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, +) from ..db_engine import db, CustomCommand, CustomCommandCreator -logger = logging.getLogger('database_api') +logger = logging.getLogger("database_api") -router = APIRouter( - prefix='/api/v3/custom_commands', - tags=['custom_commands'] -) +router = APIRouter(prefix="/api/v3/custom_commands", tags=["custom_commands"]) # Pydantic Models for API @@ -69,9 +71,9 @@ class CustomCommandStatsResponse(BaseModel): def convert_datetime_to_iso(data_dict, fields=None): """Convert datetime objects to ISO strings in a dictionary.""" if fields is None: - fields = ['created_at', 'updated_at', 'last_used'] + fields = ["created_at", "updated_at", "last_used"] for field in fields: - if data_dict.get(field) and hasattr(data_dict[field], 'isoformat'): + if data_dict.get(field) and hasattr(data_dict[field], "isoformat"): data_dict[field] = data_dict[field].isoformat() return data_dict @@ -83,9 +85,11 @@ def update_creator_stats(creator_id: int): return total = CustomCommand.select().where(CustomCommand.creator == creator).count() - active = CustomCommand.select().where( - (CustomCommand.creator == creator) & (CustomCommand.is_active == True) - ).count() + active = ( + CustomCommand.select() + .where((CustomCommand.creator == creator) & (CustomCommand.is_active == True)) + .count() + ) creator.total_commands = total creator.active_commands = active @@ -94,46 +98,46 @@ def update_creator_stats(creator_id: int): def get_custom_command_by_name(name: str): """Get a custom command by name with creator info""" - command = CustomCommand.select( - CustomCommand, - CustomCommandCreator - ).join( - CustomCommandCreator, on=(CustomCommand.creator == CustomCommandCreator.id) - ).where( - fn.LOWER(CustomCommand.name) == name.lower() - ).first() + command = ( + CustomCommand.select(CustomCommand, CustomCommandCreator) + .join( + CustomCommandCreator, on=(CustomCommand.creator == CustomCommandCreator.id) + ) + .where(fn.LOWER(CustomCommand.name) == name.lower()) + .first() + ) if command: result = model_to_dict(command, recurse=False) # Ensure creator_id is in the result (it should be from model_to_dict, but make sure) - result['creator_id'] = command.creator.id - result['creator_discord_id'] = command.creator.discord_id - result['creator_username'] = command.creator.username - result['creator_display_name'] = command.creator.display_name + result["creator_id"] = command.creator.id + result["creator_discord_id"] = command.creator.discord_id + result["creator_username"] = command.creator.username + result["creator_display_name"] = command.creator.display_name return result return None def get_custom_command_by_id(command_id: int): """Get a custom command by ID with creator info""" - command = CustomCommand.select( - CustomCommand, - CustomCommandCreator - ).join( - CustomCommandCreator, on=(CustomCommand.creator == CustomCommandCreator.id) - ).where( - CustomCommand.id == command_id - ).first() + command = ( + CustomCommand.select(CustomCommand, CustomCommandCreator) + .join( + CustomCommandCreator, on=(CustomCommand.creator == CustomCommandCreator.id) + ) + .where(CustomCommand.id == command_id) + .first() + ) if command: result = model_to_dict(command, recurse=False) - result['creator_db_id'] = command.creator.id - result['creator_discord_id'] = command.creator.discord_id - result['creator_username'] = command.creator.username - result['creator_display_name'] = command.creator.display_name - result['creator_created_at'] = command.creator.created_at - result['total_commands'] = command.creator.total_commands - result['active_commands'] = command.creator.active_commands + result["creator_db_id"] = command.creator.id + result["creator_discord_id"] = command.creator.discord_id + result["creator_username"] = command.creator.username + result["creator_display_name"] = command.creator.display_name + result["creator_created_at"] = command.creator.created_at + result["total_commands"] = command.creator.total_commands + result["active_commands"] = command.creator.active_commands return result return None @@ -141,12 +145,12 @@ def get_custom_command_by_id(command_id: int): def create_custom_command(command_data: dict) -> int: """Create a new custom command and return its ID""" # Convert tags list to JSON if present - if 'tags' in command_data and isinstance(command_data['tags'], list): - command_data['tags'] = json.dumps(command_data['tags']) + if "tags" in command_data and isinstance(command_data["tags"], list): + command_data["tags"] = json.dumps(command_data["tags"]) # Set created_at if not provided - if 'created_at' not in command_data: - command_data['created_at'] = datetime.now() + if "created_at" not in command_data: + command_data["created_at"] = datetime.now() command = CustomCommand.create(**command_data) return command.id @@ -155,8 +159,8 @@ def create_custom_command(command_data: dict) -> int: def update_custom_command(command_id: int, update_data: dict): """Update an existing custom command""" # Convert tags list to JSON if present - if 'tags' in update_data and isinstance(update_data['tags'], list): - update_data['tags'] = json.dumps(update_data['tags']) + if "tags" in update_data and isinstance(update_data["tags"], list): + update_data["tags"] = json.dumps(update_data["tags"]) query = CustomCommand.update(**update_data).where(CustomCommand.id == command_id) query.execute() @@ -170,7 +174,9 @@ def delete_custom_command(command_id: int): def get_creator_by_discord_id(discord_id: int): """Get a creator by Discord ID""" - creator = CustomCommandCreator.get_or_none(CustomCommandCreator.discord_id == str(discord_id)) + creator = CustomCommandCreator.get_or_none( + CustomCommandCreator.discord_id == str(discord_id) + ) if creator: return model_to_dict(creator) return None @@ -178,8 +184,8 @@ def get_creator_by_discord_id(discord_id: int): def create_creator(creator_data: dict) -> int: """Create a new creator and return their ID""" - if 'created_at' not in creator_data: - creator_data['created_at'] = datetime.now() + if "created_at" not in creator_data: + creator_data["created_at"] = datetime.now() creator = CustomCommandCreator.create(**creator_data) return creator.id @@ -187,7 +193,8 @@ def create_creator(creator_data: dict) -> int: # API Endpoints -@router.get('') + +@router.get("") @handle_db_errors async def get_custom_commands( name: Optional[str] = None, @@ -195,16 +202,16 @@ async def get_custom_commands( min_uses: Optional[int] = None, max_days_unused: Optional[int] = None, is_active: Optional[bool] = True, - sort: Optional[str] = 'name', + sort: Optional[str] = "name", page: int = Query(1, ge=1), - page_size: int = Query(25, ge=1, le=100) + page_size: int = Query(25, ge=1, le=100), ): """Get custom commands with filtering and pagination""" try: # Build WHERE clause where_conditions = [] params = [] - + if name is not None: where_conditions.append("cc.name ILIKE %s") params.append(f"%{name}%") @@ -225,28 +232,30 @@ async def get_custom_commands( if is_active is not None: where_conditions.append("cc.is_active = %s") params.append(is_active) - - where_clause = "WHERE " + " AND ".join(where_conditions) if where_conditions else "" - + + where_clause = ( + "WHERE " + " AND ".join(where_conditions) if where_conditions else "" + ) + # Build ORDER BY clause sort_mapping = { - 'name': 'cc.name', - 'created_at': 'cc.created_at', - 'last_used': 'cc.last_used', - 'use_count': 'cc.use_count', - 'creator': 'creator.username' + "name": "cc.name", + "created_at": "cc.created_at", + "last_used": "cc.last_used", + "use_count": "cc.use_count", + "creator": "creator.username", } - - if sort.startswith('-'): - order_direction = 'DESC' + + if sort.startswith("-"): + order_direction = "DESC" sort_field = sort[1:] else: - order_direction = 'ASC' + order_direction = "ASC" sort_field = sort - - order_by = sort_mapping.get(sort_field, 'cc.name') + + order_by = sort_mapping.get(sort_field, "cc.name") order_clause = f"ORDER BY {order_by} {order_direction}" - + # Get total count count_sql = f""" SELECT COUNT(*) @@ -255,11 +264,11 @@ async def get_custom_commands( {where_clause} """ total_count = db.execute_sql(count_sql, params).fetchone()[0] - + # Calculate pagination offset = (page - 1) * page_size total_pages = (total_count + page_size - 1) // page_size - + # Get commands sql = f""" SELECT cc.*, creator.discord_id as creator_discord_id, @@ -275,51 +284,61 @@ async def get_custom_commands( params.extend([page_size, offset]) cursor3 = db.execute_sql(sql, params) results = cursor3.fetchall() - + # Convert to CustomCommandModel objects with creator info commands = [] if results: columns3 = [desc[0] for desc in cursor3.description] for row in results: command_dict = dict(zip(columns3, row)) - + # Parse tags if they exist - if command_dict.get('tags'): + if command_dict.get("tags"): try: - command_dict['tags'] = json.loads(command_dict['tags']) + command_dict["tags"] = json.loads(command_dict["tags"]) except: - command_dict['tags'] = [] - + command_dict["tags"] = [] + # Get full creator information - creator_id = command_dict['creator_id'] - creator_cursor = db.execute_sql("SELECT * FROM custom_command_creators WHERE id = %s", (creator_id,)) + creator_id = command_dict["creator_id"] + creator_cursor = db.execute_sql( + "SELECT * FROM custom_command_creators WHERE id = %s", (creator_id,) + ) creator_result = creator_cursor.fetchone() - + if creator_result: # Create complete creator object creator_columns = [desc[0] for desc in creator_cursor.description] creator_dict = dict(zip(creator_columns, creator_result)) # Convert datetime to ISO string - if creator_dict.get('created_at') and hasattr(creator_dict['created_at'], 'isoformat'): - creator_dict['created_at'] = creator_dict['created_at'].isoformat() + if creator_dict.get("created_at") and hasattr( + creator_dict["created_at"], "isoformat" + ): + creator_dict["created_at"] = creator_dict[ + "created_at" + ].isoformat() try: creator_model = CustomCommandCreatorModel(**creator_dict) - command_dict['creator'] = creator_model + command_dict["creator"] = creator_model except Exception as e: - logger.error(f"Error creating CustomCommandCreatorModel: {e}, data: {creator_dict}") - command_dict['creator'] = None + logger.error( + f"Error creating CustomCommandCreatorModel: {e}, data: {creator_dict}" + ) + command_dict["creator"] = None else: # No creator found, set to None - command_dict['creator'] = None - + command_dict["creator"] = None + # Remove the individual creator fields now that we have the creator object - command_dict.pop('creator_discord_id', None) - command_dict.pop('creator_username', None) - command_dict.pop('creator_display_name', None) + command_dict.pop("creator_discord_id", None) + command_dict.pop("creator_username", None) + command_dict.pop("creator_display_name", None) # Convert datetime fields to ISO strings - for field in ['created_at', 'updated_at', 'last_used']: - if command_dict.get(field) and hasattr(command_dict[field], 'isoformat'): + for field in ["created_at", "updated_at", "last_used"]: + if command_dict.get(field) and hasattr( + command_dict[field], "isoformat" + ): command_dict[field] = command_dict[field].isoformat() # Create CustomCommandModel instance @@ -327,19 +346,21 @@ async def get_custom_commands( command_model = CustomCommandModel(**command_dict) commands.append(command_model) except Exception as e: - logger.error(f"Error creating CustomCommandModel: {e}, data: {command_dict}") + logger.error( + f"Error creating CustomCommandModel: {e}, data: {command_dict}" + ) # Skip invalid commands rather than failing the entire request continue - + return CustomCommandListResponse( custom_commands=commands, total_count=total_count, page=page, page_size=page_size, total_pages=total_pages, - has_more=page < total_pages + has_more=page < total_pages, ) - + except Exception as e: logger.error(f"Error getting custom commands: {e}") raise HTTPException(status_code=500, detail=str(e)) @@ -350,30 +371,31 @@ async def get_custom_commands( # Move this route to after the specific string routes -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def create_custom_command_endpoint( - command: CustomCommandModel, - token: str = Depends(oauth2_scheme) + command: CustomCommandModel, token: str = Depends(oauth2_scheme) ): """Create a new custom command""" if not valid_token(token): - logger.warning(f'create_custom_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') - + logger.warning(f"create_custom_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") + try: # Check if command name already exists existing = get_custom_command_by_name(command.name) if existing: - raise HTTPException(status_code=409, detail=f"Command '{command.name}' already exists") - + raise HTTPException( + status_code=409, detail=f"Command '{command.name}' already exists" + ) + # Create the command - command_data = command.model_dump(exclude={'id', 'creator'}) + command_data = command.model_dump(exclude={"id", "creator"}) command_id = create_custom_command(command_data) - + # Update creator stats update_creator_stats(command.creator_id) - + # Return the created command result = get_custom_command_by_id(command_id) command_dict = dict(result) @@ -381,28 +403,28 @@ async def create_custom_command_endpoint( # Convert datetime fields convert_datetime_to_iso(command_dict) - if command_dict.get('tags'): + if command_dict.get("tags"): try: - command_dict['tags'] = json.loads(command_dict['tags']) + command_dict["tags"] = json.loads(command_dict["tags"]) except: - command_dict['tags'] = [] + command_dict["tags"] = [] - creator_created_at = command_dict.pop('creator_created_at') - if hasattr(creator_created_at, 'isoformat'): + creator_created_at = command_dict.pop("creator_created_at") + if hasattr(creator_created_at, "isoformat"): creator_created_at = creator_created_at.isoformat() - command_dict['creator'] = { - 'id': command_dict.pop('creator_db_id'), - 'discord_id': command_dict.pop('creator_discord_id'), - 'username': command_dict.pop('creator_username'), - 'display_name': command_dict.pop('creator_display_name'), - 'created_at': creator_created_at, - 'total_commands': command_dict.pop('total_commands'), - 'active_commands': command_dict.pop('active_commands') + command_dict["creator"] = { + "id": command_dict.pop("creator_db_id"), + "discord_id": command_dict.pop("creator_discord_id"), + "username": command_dict.pop("creator_username"), + "display_name": command_dict.pop("creator_display_name"), + "created_at": creator_created_at, + "total_commands": command_dict.pop("total_commands"), + "active_commands": command_dict.pop("active_commands"), } return command_dict - + except HTTPException: raise except Exception as e: @@ -412,29 +434,29 @@ async def create_custom_command_endpoint( db.close() -@router.put('/{command_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.put("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def update_custom_command_endpoint( - command_id: int, - command: CustomCommandModel, - token: str = Depends(oauth2_scheme) + command_id: int, command: CustomCommandModel, token: str = Depends(oauth2_scheme) ): """Update an existing custom command""" if not valid_token(token): - logger.warning(f'update_custom_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') - + logger.warning(f"update_custom_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") + try: # Check if command exists existing = get_custom_command_by_id(command_id) if not existing: - raise HTTPException(status_code=404, detail=f"Custom command {command_id} not found") - + raise HTTPException( + status_code=404, detail=f"Custom command {command_id} not found" + ) + # Update the command - update_data = command.model_dump(exclude={'id', 'creator'}) - update_data['updated_at'] = datetime.now().isoformat() + update_data = command.model_dump(exclude={"id", "creator"}) + update_data["updated_at"] = datetime.now().isoformat() update_custom_command(command_id, update_data) - + # Return updated command result = get_custom_command_by_id(command_id) command_dict = dict(result) @@ -442,24 +464,24 @@ async def update_custom_command_endpoint( # Convert datetime fields convert_datetime_to_iso(command_dict) - if command_dict.get('tags'): + if command_dict.get("tags"): try: - command_dict['tags'] = json.loads(command_dict['tags']) + command_dict["tags"] = json.loads(command_dict["tags"]) except: - command_dict['tags'] = [] + command_dict["tags"] = [] - creator_created_at = command_dict.pop('creator_created_at') - if hasattr(creator_created_at, 'isoformat'): + creator_created_at = command_dict.pop("creator_created_at") + if hasattr(creator_created_at, "isoformat"): creator_created_at = creator_created_at.isoformat() - command_dict['creator'] = { - 'id': command_dict.pop('creator_db_id'), - 'discord_id': command_dict.pop('creator_discord_id'), - 'username': command_dict.pop('creator_username'), - 'display_name': command_dict.pop('creator_display_name'), - 'created_at': creator_created_at, - 'total_commands': command_dict.pop('total_commands'), - 'active_commands': command_dict.pop('active_commands') + command_dict["creator"] = { + "id": command_dict.pop("creator_db_id"), + "discord_id": command_dict.pop("creator_discord_id"), + "username": command_dict.pop("creator_username"), + "display_name": command_dict.pop("creator_display_name"), + "created_at": creator_created_at, + "total_commands": command_dict.pop("total_commands"), + "active_commands": command_dict.pop("active_commands"), } return command_dict @@ -473,7 +495,7 @@ async def update_custom_command_endpoint( db.close() -@router.patch('/{command_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def patch_custom_command( command_id: int, @@ -483,41 +505,43 @@ async def patch_custom_command( use_count: Optional[int] = None, last_used: Optional[str] = None, warning_sent: Optional[bool] = None, - is_active: Optional[bool] = None + is_active: Optional[bool] = None, ): """Partially update a custom command""" if not valid_token(token): - logger.warning(f'patch_custom_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') - + logger.warning(f"patch_custom_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") + try: # Check if command exists existing = get_custom_command_by_id(command_id) if not existing: - raise HTTPException(status_code=404, detail=f"Custom command {command_id} not found") - + raise HTTPException( + status_code=404, detail=f"Custom command {command_id} not found" + ) + # Build update data update_data = {} if content is not None: - update_data['content'] = content - update_data['updated_at'] = datetime.now().isoformat() + update_data["content"] = content + update_data["updated_at"] = datetime.now().isoformat() if tags is not None: - update_data['tags'] = tags + update_data["tags"] = tags if use_count is not None: - update_data['use_count'] = use_count + update_data["use_count"] = use_count if last_used is not None: - update_data['last_used'] = last_used + update_data["last_used"] = last_used if warning_sent is not None: - update_data['warning_sent'] = warning_sent + update_data["warning_sent"] = warning_sent if is_active is not None: - update_data['is_active'] = is_active - + update_data["is_active"] = is_active + if not update_data: raise HTTPException(status_code=400, detail="No fields to update") - + # Update the command update_custom_command(command_id, update_data) - + # Return updated command result = get_custom_command_by_id(command_id) command_dict = dict(result) @@ -525,28 +549,28 @@ async def patch_custom_command( # Convert datetime fields to ISO strings convert_datetime_to_iso(command_dict) - if command_dict.get('tags'): + if command_dict.get("tags"): try: - command_dict['tags'] = json.loads(command_dict['tags']) + command_dict["tags"] = json.loads(command_dict["tags"]) except: - command_dict['tags'] = [] + command_dict["tags"] = [] - creator_created_at = command_dict.pop('creator_created_at') - if hasattr(creator_created_at, 'isoformat'): + creator_created_at = command_dict.pop("creator_created_at") + if hasattr(creator_created_at, "isoformat"): creator_created_at = creator_created_at.isoformat() - command_dict['creator'] = { - 'id': command_dict.pop('creator_db_id'), - 'discord_id': command_dict.pop('creator_discord_id'), - 'username': command_dict.pop('creator_username'), - 'display_name': command_dict.pop('creator_display_name'), - 'created_at': creator_created_at, - 'total_commands': command_dict.pop('total_commands'), - 'active_commands': command_dict.pop('active_commands') + command_dict["creator"] = { + "id": command_dict.pop("creator_db_id"), + "discord_id": command_dict.pop("creator_discord_id"), + "username": command_dict.pop("creator_username"), + "display_name": command_dict.pop("creator_display_name"), + "created_at": creator_created_at, + "total_commands": command_dict.pop("total_commands"), + "active_commands": command_dict.pop("active_commands"), } - + return command_dict - + except HTTPException: raise except Exception as e: @@ -556,33 +580,34 @@ async def patch_custom_command( db.close() -@router.delete('/{command_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_custom_command_endpoint( - command_id: int, - token: str = Depends(oauth2_scheme) + command_id: int, token: str = Depends(oauth2_scheme) ): """Delete a custom command""" if not valid_token(token): - logger.warning(f'delete_custom_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') - + logger.warning(f"delete_custom_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") + try: # Check if command exists existing = get_custom_command_by_id(command_id) if not existing: - raise HTTPException(status_code=404, detail=f"Custom command {command_id} not found") - - creator_id = existing['creator_db_id'] - + raise HTTPException( + status_code=404, detail=f"Custom command {command_id} not found" + ) + + creator_id = existing["creator_db_id"] + # Delete the command delete_custom_command(command_id) - + # Update creator stats update_creator_stats(creator_id) - + return {"message": f"Custom command {command_id} deleted successfully"} - + except HTTPException: raise except Exception as e: @@ -593,33 +618,35 @@ async def delete_custom_command_endpoint( # Creator endpoints -@router.get('/creators') +@router.get("/creators") @handle_db_errors async def get_creators( discord_id: Optional[int] = None, page: int = Query(1, ge=1), - page_size: int = Query(25, ge=1, le=100) + page_size: int = Query(25, ge=1, le=100), ): """Get custom command creators with optional filtering""" try: # Build WHERE clause where_conditions = [] params = [] - + if discord_id is not None: where_conditions.append("discord_id = %s") params.append(discord_id) - - where_clause = "WHERE " + " AND ".join(where_conditions) if where_conditions else "" - + + where_clause = ( + "WHERE " + " AND ".join(where_conditions) if where_conditions else "" + ) + # Get total count count_sql = f"SELECT COUNT(*) FROM custom_command_creators {where_clause}" total_count = db.execute_sql(count_sql, params).fetchone()[0] - + # Calculate pagination offset = (page - 1) * page_size total_pages = (total_count + page_size - 1) // page_size - + # Get creators sql = f""" SELECT * FROM custom_command_creators @@ -631,7 +658,7 @@ async def get_creators( params.extend([page_size, offset]) cursor = db.execute_sql(sql, params) results = cursor.fetchall() - + # Convert to dict format creators = [] if results: @@ -639,19 +666,21 @@ async def get_creators( for row in results: creator_dict = dict(zip(columns, row)) # Convert datetime to ISO string - if creator_dict.get('created_at') and hasattr(creator_dict['created_at'], 'isoformat'): - creator_dict['created_at'] = creator_dict['created_at'].isoformat() + if creator_dict.get("created_at") and hasattr( + creator_dict["created_at"], "isoformat" + ): + creator_dict["created_at"] = creator_dict["created_at"].isoformat() creators.append(creator_dict) - + return { - 'creators': creators, - 'total_count': total_count, - 'page': page, - 'page_size': page_size, - 'total_pages': total_pages, - 'has_more': page < total_pages + "creators": creators, + "total_count": total_count, + "page": page, + "page_size": page_size, + "total_pages": total_pages, + "has_more": page < total_pages, } - + except Exception as e: logger.error(f"Error getting creators: {e}") raise HTTPException(status_code=500, detail=str(e)) @@ -659,36 +688,42 @@ async def get_creators( db.close() -@router.post('/creators', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/creators", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def create_creator_endpoint( - creator: CustomCommandCreatorModel, - token: str = Depends(oauth2_scheme) + creator: CustomCommandCreatorModel, token: str = Depends(oauth2_scheme) ): """Create a new command creator""" if not valid_token(token): - logger.warning(f'create_creator - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') - + logger.warning(f"create_creator - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") + try: # Check if creator already exists existing = get_creator_by_discord_id(creator.discord_id) if existing: - raise HTTPException(status_code=409, detail=f"Creator with Discord ID {creator.discord_id} already exists") - + raise HTTPException( + status_code=409, + detail=f"Creator with Discord ID {creator.discord_id} already exists", + ) + # Create the creator - creator_data = creator.model_dump(exclude={'id'}) + creator_data = creator.model_dump(exclude={"id"}) creator_id = create_creator(creator_data) - + # Return the created creator - cursor = db.execute_sql("SELECT * FROM custom_command_creators WHERE id = %s", (creator_id,)) + cursor = db.execute_sql( + "SELECT * FROM custom_command_creators WHERE id = %s", (creator_id,) + ) result = cursor.fetchone() if result: columns = [desc[0] for desc in cursor.description] return dict(zip(columns, result)) else: - raise HTTPException(status_code=500, detail="Failed to retrieve created creator") - + raise HTTPException( + status_code=500, detail="Failed to retrieve created creator" + ) + except HTTPException: raise except Exception as e: @@ -698,20 +733,28 @@ async def create_creator_endpoint( db.close() -@router.get('/stats') +@router.get("/stats") @handle_db_errors async def get_custom_command_stats(): """Get comprehensive statistics about custom commands""" try: # Get basic counts - total_commands = db.execute_sql("SELECT COUNT(*) FROM custom_commands").fetchone()[0] - active_commands = db.execute_sql("SELECT COUNT(*) FROM custom_commands WHERE is_active = TRUE").fetchone()[0] - total_creators = db.execute_sql("SELECT COUNT(*) FROM custom_command_creators").fetchone()[0] + total_commands = db.execute_sql( + "SELECT COUNT(*) FROM custom_commands" + ).fetchone()[0] + active_commands = db.execute_sql( + "SELECT COUNT(*) FROM custom_commands WHERE is_active = TRUE" + ).fetchone()[0] + total_creators = db.execute_sql( + "SELECT COUNT(*) FROM custom_command_creators" + ).fetchone()[0] # Get total uses - total_uses_result = db.execute_sql("SELECT SUM(use_count) FROM custom_commands WHERE is_active = TRUE").fetchone() + total_uses_result = db.execute_sql( + "SELECT SUM(use_count) FROM custom_commands WHERE is_active = TRUE" + ).fetchone() total_uses = total_uses_result[0] if total_uses_result[0] else 0 - + # Get most popular command cursor = db.execute_sql(""" SELECT cc.*, creator.discord_id as creator_discord_id, @@ -723,65 +766,80 @@ async def get_custom_command_stats(): ORDER BY cc.use_count DESC LIMIT 1 """) - + most_popular_result = cursor.fetchone() most_popular_command = None if most_popular_result: columns = [desc[0] for desc in cursor.description] command_dict = dict(zip(columns, most_popular_result)) # Convert datetime fields to ISO strings - for field in ['created_at', 'updated_at', 'last_used']: - if command_dict.get(field) and hasattr(command_dict[field], 'isoformat'): + for field in ["created_at", "updated_at", "last_used"]: + if command_dict.get(field) and hasattr( + command_dict[field], "isoformat" + ): command_dict[field] = command_dict[field].isoformat() - if command_dict.get('tags'): + if command_dict.get("tags"): try: - command_dict['tags'] = json.loads(command_dict['tags']) + command_dict["tags"] = json.loads(command_dict["tags"]) except: - command_dict['tags'] = [] - command_dict['creator'] = { - 'discord_id': command_dict.pop('creator_discord_id'), - 'username': command_dict.pop('creator_username'), - 'display_name': command_dict.pop('creator_display_name') + command_dict["tags"] = [] + command_dict["creator"] = { + "discord_id": command_dict.pop("creator_discord_id"), + "username": command_dict.pop("creator_username"), + "display_name": command_dict.pop("creator_display_name"), } most_popular_command = command_dict - + # Get most active creator cursor2 = db.execute_sql(""" SELECT * FROM custom_command_creators ORDER BY active_commands DESC LIMIT 1 """) - + most_active_creator_result = cursor2.fetchone() most_active_creator = None if most_active_creator_result: columns2 = [desc[0] for desc in cursor2.description] most_active_creator = dict(zip(columns2, most_active_creator_result)) # Convert datetime to ISO string - if most_active_creator.get('created_at') and hasattr(most_active_creator['created_at'], 'isoformat'): - most_active_creator['created_at'] = most_active_creator['created_at'].isoformat() - + if most_active_creator.get("created_at") and hasattr( + most_active_creator["created_at"], "isoformat" + ): + most_active_creator["created_at"] = most_active_creator[ + "created_at" + ].isoformat() + # Get recent commands count (last 7 days) week_ago = (datetime.now() - timedelta(days=7)).isoformat() - recent_count = db.execute_sql(""" + recent_count = db.execute_sql( + """ SELECT COUNT(*) FROM custom_commands WHERE created_at >= %s AND is_active = TRUE - """, (week_ago,)).fetchone()[0] + """, + (week_ago,), + ).fetchone()[0] # Get cleanup stats sixty_days_ago = (datetime.now() - timedelta(days=60)).isoformat() ninety_days_ago = (datetime.now() - timedelta(days=90)).isoformat() - warning_count = db.execute_sql(""" + warning_count = db.execute_sql( + """ SELECT COUNT(*) FROM custom_commands WHERE last_used < %s AND warning_sent = FALSE AND is_active = TRUE - """, (sixty_days_ago,)).fetchone()[0] + """, + (sixty_days_ago,), + ).fetchone()[0] - deletion_count = db.execute_sql(""" + deletion_count = db.execute_sql( + """ SELECT COUNT(*) FROM custom_commands WHERE last_used < %s AND is_active = TRUE - """, (ninety_days_ago,)).fetchone()[0] - + """, + (ninety_days_ago,), + ).fetchone()[0] + return CustomCommandStatsResponse( total_commands=total_commands, active_commands=active_commands, @@ -791,9 +849,9 @@ async def get_custom_command_stats(): most_active_creator=most_active_creator, recent_commands_count=recent_count, commands_needing_warning=warning_count, - commands_eligible_for_deletion=deletion_count + commands_eligible_for_deletion=deletion_count, ) - + except Exception as e: logger.error(f"Error getting custom command stats: {e}") raise HTTPException(status_code=500, detail=str(e)) @@ -802,7 +860,7 @@ async def get_custom_command_stats(): # Special endpoints for Discord bot integration -@router.get('/by_name/{command_name}') +@router.get("/by_name/{command_name}") @handle_db_errors async def get_custom_command_by_name_endpoint(command_name: str): """Get a custom command by name (for Discord bot execution)""" @@ -810,7 +868,9 @@ async def get_custom_command_by_name_endpoint(command_name: str): result = get_custom_command_by_name(command_name) if not result: - raise HTTPException(status_code=404, detail=f"Custom command '{command_name}' not found") + raise HTTPException( + status_code=404, detail=f"Custom command '{command_name}' not found" + ) command_dict = dict(result) @@ -818,41 +878,45 @@ async def get_custom_command_by_name_endpoint(command_name: str): convert_datetime_to_iso(command_dict) # Parse tags - if command_dict.get('tags'): + if command_dict.get("tags"): try: - command_dict['tags'] = json.loads(command_dict['tags']) + command_dict["tags"] = json.loads(command_dict["tags"]) except: - command_dict['tags'] = [] + command_dict["tags"] = [] # Add creator info - get full creator record - creator_id = command_dict['creator_id'] - creator_cursor = db.execute_sql("SELECT * FROM custom_command_creators WHERE id = %s", (creator_id,)) + creator_id = command_dict["creator_id"] + creator_cursor = db.execute_sql( + "SELECT * FROM custom_command_creators WHERE id = %s", (creator_id,) + ) creator_result = creator_cursor.fetchone() if creator_result: creator_columns = [desc[0] for desc in creator_cursor.description] creator_dict = dict(zip(creator_columns, creator_result)) # Convert creator datetime to ISO string - convert_datetime_to_iso(creator_dict, fields=['created_at']) - command_dict['creator'] = creator_dict + convert_datetime_to_iso(creator_dict, fields=["created_at"]) + command_dict["creator"] = creator_dict else: # Fallback to basic info if full creator not found - command_dict['creator'] = { - 'id': creator_id, - 'discord_id': command_dict.pop('creator_discord_id'), - 'username': command_dict.pop('creator_username'), - 'display_name': command_dict.pop('creator_display_name'), - 'created_at': command_dict['created_at'], # Use command creation as fallback - 'total_commands': 0, - 'active_commands': 0 + command_dict["creator"] = { + "id": creator_id, + "discord_id": command_dict.pop("creator_discord_id"), + "username": command_dict.pop("creator_username"), + "display_name": command_dict.pop("creator_display_name"), + "created_at": command_dict[ + "created_at" + ], # Use command creation as fallback + "total_commands": 0, + "active_commands": 0, } - + # Remove the duplicate fields - command_dict.pop('creator_discord_id', None) - command_dict.pop('creator_username', None) - command_dict.pop('creator_display_name', None) - + command_dict.pop("creator_discord_id", None) + command_dict.pop("creator_username", None) + command_dict.pop("creator_display_name", None) + return command_dict - + except HTTPException: raise except Exception as e: @@ -862,35 +926,36 @@ async def get_custom_command_by_name_endpoint(command_name: str): db.close() -@router.patch('/by_name/{command_name}/execute', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/by_name/{command_name}/execute", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def execute_custom_command( - command_name: str, - token: str = Depends(oauth2_scheme) + command_name: str, token: str = Depends(oauth2_scheme) ): """Execute a custom command and update usage statistics""" if not valid_token(token): - logger.warning(f'execute_custom_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') - + logger.warning(f"execute_custom_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") + try: result = get_custom_command_by_name(command_name) - + if not result: - raise HTTPException(status_code=404, detail=f"Custom command '{command_name}' not found") - + raise HTTPException( + status_code=404, detail=f"Custom command '{command_name}' not found" + ) + command_dict = dict(result) - command_id = command_dict['id'] - + command_id = command_dict["id"] + # Update usage statistics update_data = { - 'last_used': datetime.now().isoformat(), - 'use_count': command_dict['use_count'] + 1, - 'warning_sent': False # Reset warning on use + "last_used": datetime.now().isoformat(), + "use_count": command_dict["use_count"] + 1, + "warning_sent": False, # Reset warning on use } - + update_custom_command(command_id, update_data) - + # Return updated command - get_custom_command_by_id already has all creator info updated_result = get_custom_command_by_id(command_id) updated_dict = dict(updated_result) @@ -898,29 +963,29 @@ async def execute_custom_command( # Convert datetime fields to ISO strings convert_datetime_to_iso(updated_dict) - if updated_dict.get('tags'): + if updated_dict.get("tags"): try: - updated_dict['tags'] = json.loads(updated_dict['tags']) + updated_dict["tags"] = json.loads(updated_dict["tags"]) except: - updated_dict['tags'] = [] + updated_dict["tags"] = [] # Build creator object from the fields returned by get_custom_command_by_id - creator_created_at = updated_dict.pop('creator_created_at') - if hasattr(creator_created_at, 'isoformat'): + creator_created_at = updated_dict.pop("creator_created_at") + if hasattr(creator_created_at, "isoformat"): creator_created_at = creator_created_at.isoformat() - updated_dict['creator'] = { - 'id': updated_dict.pop('creator_db_id'), - 'discord_id': updated_dict.pop('creator_discord_id'), - 'username': updated_dict.pop('creator_username'), - 'display_name': updated_dict.pop('creator_display_name'), - 'created_at': creator_created_at, - 'total_commands': updated_dict.pop('total_commands'), - 'active_commands': updated_dict.pop('active_commands') + updated_dict["creator"] = { + "id": updated_dict.pop("creator_db_id"), + "discord_id": updated_dict.pop("creator_discord_id"), + "username": updated_dict.pop("creator_username"), + "display_name": updated_dict.pop("creator_display_name"), + "created_at": creator_created_at, + "total_commands": updated_dict.pop("total_commands"), + "active_commands": updated_dict.pop("active_commands"), } return updated_dict - + except HTTPException: raise except Exception as e: @@ -930,31 +995,36 @@ async def execute_custom_command( db.close() -@router.get('/autocomplete') +@router.get("/autocomplete") @handle_db_errors async def get_command_names_for_autocomplete( - partial_name: str = "", - limit: int = Query(25, ge=1, le=100) + partial_name: str = "", limit: int = Query(25, ge=1, le=100) ): """Get command names for Discord autocomplete""" try: if partial_name: - results = db.execute_sql(""" + results = db.execute_sql( + """ SELECT name FROM custom_commands WHERE is_active = TRUE AND name ILIKE %s ORDER BY name LIMIT %s - """, (f"%{partial_name}%", limit)).fetchall() + """, + (f"%{partial_name}%", limit), + ).fetchall() else: - results = db.execute_sql(""" + results = db.execute_sql( + """ SELECT name FROM custom_commands WHERE is_active = TRUE ORDER BY name LIMIT %s - """, (limit,)).fetchall() - + """, + (limit,), + ).fetchall() + return [row[0] for row in results] - + except Exception as e: logger.error(f"Error getting command names for autocomplete: {e}") raise HTTPException(status_code=500, detail=str(e)) @@ -962,7 +1032,7 @@ async def get_command_names_for_autocomplete( db.close() -@router.get('/{command_id}') +@router.get("/{command_id}") @handle_db_errors async def get_custom_command(command_id: int): """Get a single custom command by ID""" @@ -970,7 +1040,9 @@ async def get_custom_command(command_id: int): result = get_custom_command_by_id(command_id) if not result: - raise HTTPException(status_code=404, detail=f"Custom command {command_id} not found") + raise HTTPException( + status_code=404, detail=f"Custom command {command_id} not found" + ) command_dict = dict(result) @@ -978,33 +1050,33 @@ async def get_custom_command(command_id: int): convert_datetime_to_iso(command_dict) # Parse tags - if command_dict.get('tags'): + if command_dict.get("tags"): try: - command_dict['tags'] = json.loads(command_dict['tags']) + command_dict["tags"] = json.loads(command_dict["tags"]) except: - command_dict['tags'] = [] + command_dict["tags"] = [] - creator_created_at = command_dict.pop('creator_created_at') - if hasattr(creator_created_at, 'isoformat'): + creator_created_at = command_dict.pop("creator_created_at") + if hasattr(creator_created_at, "isoformat"): creator_created_at = creator_created_at.isoformat() # Add creator info - command_dict['creator'] = { - 'id': command_dict.pop('creator_db_id'), - 'discord_id': command_dict.pop('creator_discord_id'), - 'username': command_dict.pop('creator_username'), - 'display_name': command_dict.pop('creator_display_name'), - 'created_at': creator_created_at, - 'total_commands': command_dict.pop('total_commands'), - 'active_commands': command_dict.pop('active_commands') + command_dict["creator"] = { + "id": command_dict.pop("creator_db_id"), + "discord_id": command_dict.pop("creator_discord_id"), + "username": command_dict.pop("creator_username"), + "display_name": command_dict.pop("creator_display_name"), + "created_at": creator_created_at, + "total_commands": command_dict.pop("total_commands"), + "active_commands": command_dict.pop("active_commands"), } - + return command_dict - + except HTTPException: raise except Exception as e: logger.error(f"Error getting custom command {command_id}: {e}") raise HTTPException(status_code=500, detail=str(e)) finally: - db.close() \ No newline at end of file + db.close() diff --git a/app/routers_v3/decisions.py b/app/routers_v3/decisions.py index 6170ff2..a59b16f 100644 --- a/app/routers_v3/decisions.py +++ b/app/routers_v3/decisions.py @@ -4,15 +4,26 @@ import copy import logging import pydantic -from ..db_engine import db, Decision, StratGame, Player, model_to_dict, chunked, fn, Team -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/decisions', - tags=['decisions'] +from ..db_engine import ( + db, + Decision, + StratGame, + Player, + model_to_dict, + chunked, + fn, + Team, ) +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, +) + +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/decisions", tags=["decisions"]) class DecisionModel(pydantic.BaseModel): @@ -43,17 +54,31 @@ class DecisionReturnList(pydantic.BaseModel): decisions: list[DecisionModel] -@router.get('') +@router.get("") @handle_db_errors async def get_decisions( - season: list = Query(default=None), week: list = Query(default=None), game_num: list = Query(default=None), - s_type: Literal['regular', 'post', 'all', None] = None, team_id: list = Query(default=None), - week_start: Optional[int] = None, week_end: Optional[int] = None, win: Optional[int] = None, - loss: Optional[int] = None, hold: Optional[int] = None, save: Optional[int] = None, - b_save: Optional[int] = None, irunners: list = Query(default=None), irunners_scored: list = Query(default=None), - game_id: list = Query(default=None), player_id: list = Query(default=None), - limit: Optional[int] = None, short_output: Optional[bool] = False): - all_dec = Decision.select().order_by(-Decision.season, -Decision.week, -Decision.game_num) + season: list = Query(default=None), + week: list = Query(default=None), + game_num: list = Query(default=None), + s_type: Literal["regular", "post", "all", None] = None, + team_id: list = Query(default=None), + week_start: Optional[int] = None, + week_end: Optional[int] = None, + win: Optional[int] = None, + loss: Optional[int] = None, + hold: Optional[int] = None, + save: Optional[int] = None, + b_save: Optional[int] = None, + irunners: list = Query(default=None), + irunners_scored: list = Query(default=None), + game_id: list = Query(default=None), + player_id: list = Query(default=None), + limit: Optional[int] = None, + short_output: Optional[bool] = False, +): + all_dec = Decision.select().order_by( + -Decision.season, -Decision.week, -Decision.game_num + ) if season is not None: all_dec = all_dec.where(Decision.season << season) @@ -79,7 +104,8 @@ async def get_decisions( if season is not None and 8 in season or s8_teams: all_teams = Team.select().where(Team.id << team_id) all_games = StratGame.select().where( - (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams)) + (StratGame.away_team << all_teams) | (StratGame.home_team << all_teams) + ) all_dec = all_dec.where(Decision.game << all_games) else: all_teams = Team.select().where(Team.id << team_id) @@ -115,28 +141,38 @@ async def get_decisions( all_dec = all_dec.limit(limit) return_dec = { - 'count': all_dec.count(), - 'decisions': [model_to_dict(x, recurse=not short_output) for x in all_dec] + "count": all_dec.count(), + "decisions": [model_to_dict(x, recurse=not short_output) for x in all_dec], } db.close() return return_dec -@router.patch('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def patch_decision( - decision_id: int, win: Optional[int] = None, loss: Optional[int] = None, hold: Optional[int] = None, - save: Optional[int] = None, b_save: Optional[int] = None, irunners: Optional[int] = None, - irunners_scored: Optional[int] = None, rest_ip: Optional[int] = None, rest_required: Optional[int] = None, - token: str = Depends(oauth2_scheme)): + decision_id: int, + win: Optional[int] = None, + loss: Optional[int] = None, + hold: Optional[int] = None, + save: Optional[int] = None, + b_save: Optional[int] = None, + irunners: Optional[int] = None, + irunners_scored: Optional[int] = None, + rest_ip: Optional[int] = None, + rest_required: Optional[int] = None, + token: str = Depends(oauth2_scheme), +): if not valid_token(token): - logger.warning(f'patch_decision - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"patch_decision - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_dec = Decision.get_or_none(Decision.id == decision_id) if this_dec is None: db.close() - raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found') + raise HTTPException( + status_code=404, detail=f"Decision ID {decision_id} not found" + ) if win is not None: this_dec.win = win @@ -163,22 +199,28 @@ async def patch_decision( return d_result else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch decision {decision_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch decision {decision_id}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_decisions - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_decisions - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") new_dec = [] for x in dec_list.decisions: if StratGame.get_or_none(StratGame.id == x.game_id) is None: - raise HTTPException(status_code=404, detail=f'Game ID {x.game_id} not found') + raise HTTPException( + status_code=404, detail=f"Game ID {x.game_id} not found" + ) if Player.get_or_none(Player.id == x.pitcher_id) is None: - raise HTTPException(status_code=404, detail=f'Player ID {x.pitcher_id} not found') + raise HTTPException( + status_code=404, detail=f"Player ID {x.pitcher_id} not found" + ) new_dec.append(x.dict()) @@ -187,49 +229,53 @@ async def post_decisions(dec_list: DecisionList, token: str = Depends(oauth2_sch Decision.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_dec)} decisions' + return f"Inserted {len(new_dec)} decisions" -@router.delete('/{decision_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{decision_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_decision(decision_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_decision - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"delete_decision - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_dec = Decision.get_or_none(Decision.id == decision_id) if this_dec is None: db.close() - raise HTTPException(status_code=404, detail=f'Decision ID {decision_id} not found') + raise HTTPException( + status_code=404, detail=f"Decision ID {decision_id} not found" + ) count = this_dec.delete_instance() db.close() if count == 1: - return f'Decision {decision_id} has been deleted' + return f"Decision {decision_id} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Decision {decision_id} could not be deleted') + raise HTTPException( + status_code=500, detail=f"Decision {decision_id} could not be deleted" + ) -@router.delete('/game/{game_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/game/{game_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_decisions_game(game_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_decisions_game - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"delete_decisions_game - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_game = StratGame.get_or_none(StratGame.id == game_id) if not this_game: db.close() - raise HTTPException(status_code=404, detail=f'Game ID {game_id} not found') + raise HTTPException(status_code=404, detail=f"Game ID {game_id} not found") count = Decision.delete().where(Decision.game == this_game).execute() db.close() if count > 0: - return f'Deleted {count} decisions matching Game ID {game_id}' + return f"Deleted {count} decisions matching Game ID {game_id}" else: - raise HTTPException(status_code=500, detail=f'No decisions matching Game ID {game_id} were deleted') - - - + raise HTTPException( + status_code=500, + detail=f"No decisions matching Game ID {game_id} were deleted", + ) diff --git a/app/routers_v3/divisions.py b/app/routers_v3/divisions.py index e3e621a..095662a 100644 --- a/app/routers_v3/divisions.py +++ b/app/routers_v3/divisions.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, Division, Team, model_to_dict, chunked, fn -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/divisions', - tags=['divisions'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/divisions", tags=["divisions"]) + class DivisionModel(pydantic.BaseModel): division_name: str @@ -22,11 +24,15 @@ class DivisionModel(pydantic.BaseModel): season: int = 0 -@router.get('') +@router.get("") @handle_db_errors async def get_divisions( - season: int, div_name: Optional[str] = None, div_abbrev: Optional[str] = None, lg_name: Optional[str] = None, - lg_abbrev: Optional[str] = None): + season: int, + div_name: Optional[str] = None, + div_abbrev: Optional[str] = None, + lg_name: Optional[str] = None, + lg_abbrev: Optional[str] = None, +): all_divisions = Division.select().where(Division.season == season) if div_name is not None: @@ -39,39 +45,48 @@ async def get_divisions( all_divisions = all_divisions.where(Division.league_abbrev == lg_abbrev) return_div = { - 'count': all_divisions.count(), - 'divisions': [model_to_dict(x) for x in all_divisions] + "count": all_divisions.count(), + "divisions": [model_to_dict(x) for x in all_divisions], } db.close() return return_div -@router.get('/{division_id}') +@router.get("/{division_id}") @handle_db_errors async def get_one_division(division_id: int): this_div = Division.get_or_none(Division.id == division_id) if this_div is None: db.close() - raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found') + raise HTTPException( + status_code=404, detail=f"Division ID {division_id} not found" + ) r_div = model_to_dict(this_div) db.close() return r_div -@router.patch('/{division_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{division_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def patch_division( - division_id: int, div_name: Optional[str] = None, div_abbrev: Optional[str] = None, - lg_name: Optional[str] = None, lg_abbrev: Optional[str] = None, token: str = Depends(oauth2_scheme)): + division_id: int, + div_name: Optional[str] = None, + div_abbrev: Optional[str] = None, + lg_name: Optional[str] = None, + lg_abbrev: Optional[str] = None, + token: str = Depends(oauth2_scheme), +): if not valid_token(token): - logger.warning(f'patch_division - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"patch_division - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_div = Division.get_or_none(Division.id == division_id) if this_div is None: db.close() - raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found') + raise HTTPException( + status_code=404, detail=f"Division ID {division_id} not found" + ) if div_name is not None: this_div.division_name = div_name @@ -88,15 +103,19 @@ async def patch_division( return r_division else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch division {division_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch division {division_id}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def post_division(new_division: DivisionModel, token: str = Depends(oauth2_scheme)): +async def post_division( + new_division: DivisionModel, token: str = Depends(oauth2_scheme) +): if not valid_token(token): - logger.warning(f'post_division - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_division - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_division = Division(**new_division.dict()) @@ -106,27 +125,29 @@ async def post_division(new_division: DivisionModel, token: str = Depends(oauth2 return r_division else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to post division') + raise HTTPException(status_code=500, detail=f"Unable to post division") -@router.delete('/{division_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{division_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_division(division_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_division - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"delete_division - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_div = Division.get_or_none(Division.id == division_id) if this_div is None: db.close() - raise HTTPException(status_code=404, detail=f'Division ID {division_id} not found') + raise HTTPException( + status_code=404, detail=f"Division ID {division_id} not found" + ) count = this_div.delete_instance() db.close() if count == 1: - return f'Division {division_id} has been deleted' + return f"Division {division_id} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Division {division_id} could not be deleted') - - + raise HTTPException( + status_code=500, detail=f"Division {division_id} could not be deleted" + ) diff --git a/app/routers_v3/draftlist.py b/app/routers_v3/draftlist.py index 2e25f6b..4de0d7c 100644 --- a/app/routers_v3/draftlist.py +++ b/app/routers_v3/draftlist.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, DraftList, Team, model_to_dict, chunked -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/draftlist', - tags=['draftlist'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/draftlist", tags=["draftlist"]) + class DraftListModel(pydantic.BaseModel): season: int @@ -26,13 +28,16 @@ class DraftListList(pydantic.BaseModel): draft_list: List[DraftListModel] -@router.get('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.get("", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def get_draftlist( - season: Optional[int], team_id: list = Query(default=None), token: str = Depends(oauth2_scheme)): + season: Optional[int], + team_id: list = Query(default=None), + token: str = Depends(oauth2_scheme), +): if not valid_token(token): - logger.warning(f'get_draftlist - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"get_draftlist - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") all_list = DraftList.select() @@ -41,47 +46,49 @@ async def get_draftlist( 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] - } + 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}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.get("/team/{team_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def get_team_draftlist(team_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_draftlist - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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] + "count": this_list.count(), + "picks": [model_to_dict(x) for x in this_list], } db.close() return r_list -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def post_draftlist(draft_list: DraftListList, token: str = Depends(oauth2_scheme)): +async def post_draftlist( + draft_list: DraftListList, token: str = Depends(oauth2_scheme) +): if not valid_token(token): - logger.warning(f'post_draftlist - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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() @@ -93,16 +100,16 @@ async def post_draftlist(draft_list: DraftListList, token: str = Depends(oauth2_ DraftList.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_list)} list values' + return f"Inserted {len(new_list)} list values" -@router.delete('/team/{team_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/team/{team_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_draftlist(team_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_draftlist - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') - + logger.warning(f"delete_draftlist - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") + count = DraftList.delete().where(DraftList.team_id == team_id).execute() db.close() - return f'Deleted {count} list values' + return f"Deleted {count} list values" diff --git a/app/routers_v3/draftpicks.py b/app/routers_v3/draftpicks.py index d7b96b3..2214aa3 100644 --- a/app/routers_v3/draftpicks.py +++ b/app/routers_v3/draftpicks.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, DraftPick, Team, model_to_dict, chunked -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/draftpicks', - tags=['draftpicks'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/draftpicks", tags=["draftpicks"]) + class DraftPickModel(pydantic.BaseModel): overall: Optional[int] = None @@ -32,15 +34,26 @@ class DraftPickReturnList(pydantic.BaseModel): picks: list[DraftPickModel] -@router.get('') +@router.get("") @handle_db_errors 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): + 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: @@ -61,16 +74,25 @@ async def get_picks( if owner_team_id is not None: all_picks = all_picks.where( - (DraftPick.owner_id << owner_team_id) | (DraftPick.owner_id << owner_team_id) + (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) + (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 ( + 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) @@ -92,40 +114,42 @@ async def get_picks( all_picks = all_picks.limit(limit) if sort is not None: - if sort == 'order-asc': + if sort == "order-asc": all_picks = all_picks.order_by(DraftPick.overall) - elif sort == 'order-desc': + elif sort == "order-desc": all_picks = all_picks.order_by(-DraftPick.overall) - return_picks = {'count': all_picks.count(), 'picks': []} + return_picks = {"count": all_picks.count(), "picks": []} for line in all_picks: - return_picks['picks'].append(model_to_dict(line, recurse=not short_output)) + return_picks["picks"].append(model_to_dict(line, recurse=not short_output)) db.close() return return_picks -@router.get('/{pick_id}') +@router.get("/{pick_id}") @handle_db_errors 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') + raise HTTPException(status_code=404, detail=f"Pick ID {pick_id} not found") db.close() return r_pick -@router.patch('/{pick_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{pick_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def patch_pick(pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme)): +async def patch_pick( + pick_id: int, new_pick: DraftPickModel, token: str = Depends(oauth2_scheme) +): if not valid_token(token): - logger.warning(f'patch_pick - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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)) @@ -133,21 +157,23 @@ async def patch_pick(pick_id: int, new_pick: DraftPickModel, token: str = Depend return r_pick -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_picks - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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) + 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}' + detail=f"Pick # {pick.overall} already exists for season {pick.season}", ) new_picks.append(pick.dict()) @@ -157,25 +183,26 @@ async def post_picks(p_list: DraftPickList, token: str = Depends(oauth2_scheme)) DraftPick.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_picks)} picks' + return f"Inserted {len(new_picks)} picks" -@router.delete('/{pick_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{pick_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_pick(pick_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_pick - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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' + 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') - + raise HTTPException( + status_code=500, detail=f"Draft pick {pick_id} could not be deleted" + ) diff --git a/app/routers_v3/help_commands.py b/app/routers_v3/help_commands.py index 5b7126b..6d757c7 100644 --- a/app/routers_v3/help_commands.py +++ b/app/routers_v3/help_commands.py @@ -6,15 +6,17 @@ from pydantic import BaseModel, Field from playhouse.shortcuts import model_to_dict from peewee import fn -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, +) from ..db_engine import db, HelpCommand -logger = logging.getLogger('database_api') +logger = logging.getLogger("database_api") -router = APIRouter( - prefix='/api/v3/help_commands', - tags=['help_commands'] -) +router = APIRouter(prefix="/api/v3/help_commands", tags=["help_commands"]) # Pydantic Models for API @@ -52,15 +54,16 @@ class HelpCommandStatsResponse(BaseModel): # API Endpoints -@router.get('') + +@router.get("") @handle_db_errors async def get_help_commands( name: Optional[str] = None, category: Optional[str] = None, is_active: Optional[bool] = True, - sort: Optional[str] = 'name', + sort: Optional[str] = "name", page: int = Query(1, ge=1), - page_size: int = Query(25, ge=1, le=100) + page_size: int = Query(25, ge=1, le=100), ): """Get help commands with filtering and pagination""" try: @@ -82,16 +85,16 @@ async def get_help_commands( # Apply sorting sort_mapping = { - 'name': HelpCommand.name, - 'title': HelpCommand.title, - 'category': HelpCommand.category, - 'created_at': HelpCommand.created_at, - 'updated_at': HelpCommand.updated_at, - 'view_count': HelpCommand.view_count, - 'display_order': HelpCommand.display_order + "name": HelpCommand.name, + "title": HelpCommand.title, + "category": HelpCommand.category, + "created_at": HelpCommand.created_at, + "updated_at": HelpCommand.updated_at, + "view_count": HelpCommand.view_count, + "display_order": HelpCommand.display_order, } - if sort.startswith('-'): + if sort.startswith("-"): sort_field = sort[1:] order_by = sort_mapping.get(sort_field, HelpCommand.name).desc() else: @@ -111,10 +114,10 @@ async def get_help_commands( for help_cmd in query: cmd_dict = model_to_dict(help_cmd) # Convert datetime objects to ISO strings - if cmd_dict.get('created_at'): - cmd_dict['created_at'] = cmd_dict['created_at'].isoformat() - if cmd_dict.get('updated_at'): - cmd_dict['updated_at'] = cmd_dict['updated_at'].isoformat() + if cmd_dict.get("created_at"): + cmd_dict["created_at"] = cmd_dict["created_at"].isoformat() + if cmd_dict.get("updated_at"): + cmd_dict["updated_at"] = cmd_dict["updated_at"].isoformat() try: command_model = HelpCommandModel(**cmd_dict) @@ -129,7 +132,7 @@ async def get_help_commands( page=page, page_size=page_size, total_pages=total_pages, - has_more=page < total_pages + has_more=page < total_pages, ) except Exception as e: @@ -139,22 +142,23 @@ async def get_help_commands( db.close() -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def create_help_command_endpoint( - command: HelpCommandModel, - token: str = Depends(oauth2_scheme) + command: HelpCommandModel, token: str = Depends(oauth2_scheme) ): """Create a new help command""" if not valid_token(token): - logger.warning(f'create_help_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"create_help_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") try: # Check if command name already exists existing = HelpCommand.get_by_name(command.name) if existing: - raise HTTPException(status_code=409, detail=f"Help topic '{command.name}' already exists") + raise HTTPException( + status_code=409, detail=f"Help topic '{command.name}' already exists" + ) # Create the command help_cmd = HelpCommand.create( @@ -166,15 +170,15 @@ async def create_help_command_endpoint( created_at=datetime.now(), is_active=True, view_count=0, - display_order=command.display_order + display_order=command.display_order, ) # Return the created command result = model_to_dict(help_cmd) - if result.get('created_at'): - result['created_at'] = result['created_at'].isoformat() - if result.get('updated_at'): - result['updated_at'] = result['updated_at'].isoformat() + if result.get("created_at"): + result["created_at"] = result["created_at"].isoformat() + if result.get("updated_at"): + result["updated_at"] = result["updated_at"].isoformat() return result @@ -187,23 +191,23 @@ async def create_help_command_endpoint( db.close() -@router.put('/{command_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.put("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def update_help_command_endpoint( - command_id: int, - command: HelpCommandModel, - token: str = Depends(oauth2_scheme) + command_id: int, command: HelpCommandModel, token: str = Depends(oauth2_scheme) ): """Update an existing help command""" if not valid_token(token): - logger.warning(f'update_help_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"update_help_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") try: # Get the command help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) if not help_cmd: - raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") + raise HTTPException( + status_code=404, detail=f"Help command {command_id} not found" + ) # Update fields if command.title: @@ -222,10 +226,10 @@ async def update_help_command_endpoint( # Return updated command result = model_to_dict(help_cmd) - if result.get('created_at'): - result['created_at'] = result['created_at'].isoformat() - if result.get('updated_at'): - result['updated_at'] = result['updated_at'].isoformat() + if result.get("created_at"): + result["created_at"] = result["created_at"].isoformat() + if result.get("updated_at"): + result["updated_at"] = result["updated_at"].isoformat() return result @@ -238,32 +242,33 @@ async def update_help_command_endpoint( db.close() -@router.patch('/{command_id}/restore', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{command_id}/restore", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def restore_help_command_endpoint( - command_id: int, - token: str = Depends(oauth2_scheme) + command_id: int, token: str = Depends(oauth2_scheme) ): """Restore a soft-deleted help command""" if not valid_token(token): - logger.warning(f'restore_help_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"restore_help_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") try: # Get the command help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) if not help_cmd: - raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") + raise HTTPException( + status_code=404, detail=f"Help command {command_id} not found" + ) # Restore the command help_cmd.restore() # Return restored command result = model_to_dict(help_cmd) - if result.get('created_at'): - result['created_at'] = result['created_at'].isoformat() - if result.get('updated_at'): - result['updated_at'] = result['updated_at'].isoformat() + if result.get("created_at"): + result["created_at"] = result["created_at"].isoformat() + if result.get("updated_at"): + result["updated_at"] = result["updated_at"].isoformat() return result @@ -276,22 +281,23 @@ async def restore_help_command_endpoint( db.close() -@router.delete('/{command_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{command_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_help_command_endpoint( - command_id: int, - token: str = Depends(oauth2_scheme) + command_id: int, token: str = Depends(oauth2_scheme) ): """Soft delete a help command""" if not valid_token(token): - logger.warning(f'delete_help_command - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"delete_help_command - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") try: # Get the command help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) if not help_cmd: - raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") + raise HTTPException( + status_code=404, detail=f"Help command {command_id} not found" + ) # Soft delete the command help_cmd.soft_delete() @@ -307,44 +313,56 @@ async def delete_help_command_endpoint( db.close() -@router.get('/stats') +@router.get("/stats") @handle_db_errors async def get_help_command_stats(): """Get comprehensive statistics about help commands""" try: # Get basic counts total_commands = HelpCommand.select().count() - active_commands = HelpCommand.select().where(HelpCommand.is_active == True).count() + active_commands = ( + HelpCommand.select().where(HelpCommand.is_active == True).count() + ) # Get total views - total_views = HelpCommand.select(fn.SUM(HelpCommand.view_count)).where( - HelpCommand.is_active == True - ).scalar() or 0 + total_views = ( + HelpCommand.select(fn.SUM(HelpCommand.view_count)) + .where(HelpCommand.is_active == True) + .scalar() + or 0 + ) # Get most viewed command most_viewed = HelpCommand.get_most_viewed(limit=1).first() most_viewed_command = None if most_viewed: most_viewed_dict = model_to_dict(most_viewed) - if most_viewed_dict.get('created_at'): - most_viewed_dict['created_at'] = most_viewed_dict['created_at'].isoformat() - if most_viewed_dict.get('updated_at'): - most_viewed_dict['updated_at'] = most_viewed_dict['updated_at'].isoformat() + if most_viewed_dict.get("created_at"): + most_viewed_dict["created_at"] = most_viewed_dict[ + "created_at" + ].isoformat() + if most_viewed_dict.get("updated_at"): + most_viewed_dict["updated_at"] = most_viewed_dict[ + "updated_at" + ].isoformat() most_viewed_command = most_viewed_dict # Get recent commands count (last 7 days) week_ago = datetime.now() - timedelta(days=7) - recent_count = HelpCommand.select().where( - (HelpCommand.created_at >= week_ago) & - (HelpCommand.is_active == True) - ).count() + recent_count = ( + HelpCommand.select() + .where( + (HelpCommand.created_at >= week_ago) & (HelpCommand.is_active == True) + ) + .count() + ) return HelpCommandStatsResponse( total_commands=total_commands, active_commands=active_commands, total_views=int(total_views), most_viewed_command=most_viewed_command, - recent_commands_count=recent_count + recent_commands_count=recent_count, ) except Exception as e: @@ -355,24 +373,27 @@ async def get_help_command_stats(): # Special endpoints for Discord bot integration -@router.get('/by_name/{command_name}') +@router.get("/by_name/{command_name}") @handle_db_errors async def get_help_command_by_name_endpoint( - command_name: str, - include_inactive: bool = Query(False) + command_name: str, include_inactive: bool = Query(False) ): """Get a help command by name (for Discord bot)""" try: - help_cmd = HelpCommand.get_by_name(command_name, include_inactive=include_inactive) + help_cmd = HelpCommand.get_by_name( + command_name, include_inactive=include_inactive + ) if not help_cmd: - raise HTTPException(status_code=404, detail=f"Help topic '{command_name}' not found") + raise HTTPException( + status_code=404, detail=f"Help topic '{command_name}' not found" + ) result = model_to_dict(help_cmd) - if result.get('created_at'): - result['created_at'] = result['created_at'].isoformat() - if result.get('updated_at'): - result['updated_at'] = result['updated_at'].isoformat() + if result.get("created_at"): + result["created_at"] = result["created_at"].isoformat() + if result.get("updated_at"): + result["updated_at"] = result["updated_at"].isoformat() return result @@ -385,32 +406,31 @@ async def get_help_command_by_name_endpoint( db.close() -@router.patch('/by_name/{command_name}/view', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/by_name/{command_name}/view", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def increment_view_count( - command_name: str, - token: str = Depends(oauth2_scheme) -): +async def increment_view_count(command_name: str, token: str = Depends(oauth2_scheme)): """Increment view count for a help command""" if not valid_token(token): - logger.warning(f'increment_view_count - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"increment_view_count - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") try: help_cmd = HelpCommand.get_by_name(command_name) if not help_cmd: - raise HTTPException(status_code=404, detail=f"Help topic '{command_name}' not found") + raise HTTPException( + status_code=404, detail=f"Help topic '{command_name}' not found" + ) # Increment view count help_cmd.increment_view_count() # Return updated command result = model_to_dict(help_cmd) - if result.get('created_at'): - result['created_at'] = result['created_at'].isoformat() - if result.get('updated_at'): - result['updated_at'] = result['updated_at'].isoformat() + if result.get("created_at"): + result["created_at"] = result["created_at"].isoformat() + if result.get("updated_at"): + result["updated_at"] = result["updated_at"].isoformat() return result @@ -423,11 +443,10 @@ async def increment_view_count( db.close() -@router.get('/autocomplete') +@router.get("/autocomplete") @handle_db_errors async def get_help_names_for_autocomplete( - q: str = Query(""), - limit: int = Query(25, ge=1, le=100) + q: str = Query(""), limit: int = Query(25, ge=1, le=100) ): """Get help command names for Discord autocomplete""" try: @@ -438,11 +457,11 @@ async def get_help_names_for_autocomplete( # Return list of dictionaries with name, title, category return { - 'results': [ + "results": [ { - 'name': help_cmd.name, - 'title': help_cmd.title, - 'category': help_cmd.category + "name": help_cmd.name, + "title": help_cmd.title, + "category": help_cmd.category, } for help_cmd in results ] @@ -455,7 +474,7 @@ async def get_help_names_for_autocomplete( db.close() -@router.get('/{command_id}') +@router.get("/{command_id}") @handle_db_errors async def get_help_command(command_id: int): """Get a single help command by ID""" @@ -463,13 +482,15 @@ async def get_help_command(command_id: int): help_cmd = HelpCommand.get_or_none(HelpCommand.id == command_id) if not help_cmd: - raise HTTPException(status_code=404, detail=f"Help command {command_id} not found") + raise HTTPException( + status_code=404, detail=f"Help command {command_id} not found" + ) result = model_to_dict(help_cmd) - if result.get('created_at'): - result['created_at'] = result['created_at'].isoformat() - if result.get('updated_at'): - result['updated_at'] = result['updated_at'].isoformat() + if result.get("created_at"): + result["created_at"] = result["created_at"].isoformat() + if result.get("updated_at"): + result["updated_at"] = result["updated_at"].isoformat() return result diff --git a/app/routers_v3/injuries.py b/app/routers_v3/injuries.py index 7694097..77984eb 100644 --- a/app/routers_v3/injuries.py +++ b/app/routers_v3/injuries.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, Injury, Player, model_to_dict, fn -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/injuries', - tags=['injuries'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/injuries", tags=["injuries"]) + class InjuryModel(pydantic.BaseModel): season: int @@ -25,12 +27,18 @@ class InjuryModel(pydantic.BaseModel): is_active: bool = True -@router.get('') +@router.get("") @handle_db_errors async def get_injuries( - season: list = Query(default=None), player_id: list = Query(default=None), min_games: int = None, - max_games: int = None, team_id: list = Query(default=None), is_active: bool = None, - short_output: bool = False, sort: Optional[str] = 'start-asc'): + season: list = Query(default=None), + player_id: list = Query(default=None), + min_games: int = None, + max_games: int = None, + team_id: list = Query(default=None), + is_active: bool = None, + short_output: bool = False, + sort: Optional[str] = "start-asc", +): all_injuries = Injury.select() if season is not None: @@ -47,34 +55,38 @@ async def get_injuries( all_players = Player.select().where(Player.team_id << team_id) all_injuries = all_injuries.where(Injury.player << all_players) - if sort == 'return-asc': + if sort == "return-asc": all_injuries = all_injuries.order_by(Injury.end_week, Injury.end_game) - elif sort == 'return-desc': + elif sort == "return-desc": all_injuries = all_injuries.order_by(-Injury.end_week, -Injury.end_game) - elif sort == 'start-asc': + elif sort == "start-asc": all_injuries = all_injuries.order_by(Injury.start_week, Injury.start_game) - elif sort == 'start-desc': + elif sort == "start-desc": all_injuries = all_injuries.order_by(-Injury.start_week, -Injury.start_game) return_injuries = { - 'count': all_injuries.count(), - 'injuries': [model_to_dict(x, recurse=not short_output) for x in all_injuries] + "count": all_injuries.count(), + "injuries": [model_to_dict(x, recurse=not short_output) for x in all_injuries], } db.close() return return_injuries -@router.patch('/{injury_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{injury_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def patch_injury(injury_id: int, is_active: Optional[bool] = None, token: str = Depends(oauth2_scheme)): +async def patch_injury( + injury_id: int, + is_active: Optional[bool] = None, + token: str = Depends(oauth2_scheme), +): if not valid_token(token): - logger.warning(f'patch_injury - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"patch_injury - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_injury = Injury.get_or_none(Injury.id == injury_id) if this_injury is None: db.close() - raise HTTPException(status_code=404, detail=f'Injury ID {injury_id} not found') + raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found") if is_active is not None: this_injury.is_active = is_active @@ -85,15 +97,17 @@ async def patch_injury(injury_id: int, is_active: Optional[bool] = None, token: return r_injury else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch injury {injury_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch injury {injury_id}" + ) -@router.post(f'', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_injury - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_injury - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_injury = Injury(**new_injury.dict()) @@ -103,28 +117,27 @@ async def post_injury(new_injury: InjuryModel, token: str = Depends(oauth2_schem return r_injury else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to post injury') + raise HTTPException(status_code=500, detail=f"Unable to post injury") -@router.delete('/{injury_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{injury_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_injury(injury_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_injury - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"delete_injury - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_injury = Injury.get_or_none(Injury.id == injury_id) if this_injury is None: db.close() - raise HTTPException(status_code=404, detail=f'Injury ID {injury_id} not found') + raise HTTPException(status_code=404, detail=f"Injury ID {injury_id} not found") count = this_injury.delete_instance() db.close() if count == 1: - return f'Injury {injury_id} has been deleted' + return f"Injury {injury_id} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Unable to delete injury {injury_id}') - - - + raise HTTPException( + status_code=500, detail=f"Unable to delete injury {injury_id}" + ) diff --git a/app/routers_v3/keepers.py b/app/routers_v3/keepers.py index 35e3cf5..d0fafcf 100644 --- a/app/routers_v3/keepers.py +++ b/app/routers_v3/keepers.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, Keeper, Player, model_to_dict, chunked, fn -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/keepers', - tags=['keepers'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/keepers", tags=["keepers"]) + class KeeperModel(pydantic.BaseModel): season: int @@ -25,11 +27,14 @@ class KeeperList(pydantic.BaseModel): keepers: List[KeeperModel] -@router.get('') +@router.get("") @handle_db_errors 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): + 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: @@ -40,25 +45,29 @@ async def get_keepers( 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] + "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}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{keeper_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors 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)): + 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): - logger.warning(f'patch_keeper - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + raise HTTPException(status_code=404, detail=f"Keeper ID {keeper_id} not found") if season is not None: this_keeper.season = season @@ -73,15 +82,17 @@ async def patch_keeper( return r_keeper else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch keeper {keeper_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch keeper {keeper_id}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_keepers - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_keepers - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") new_keepers = [] for keeper in k_list.keepers: @@ -92,26 +103,26 @@ async def post_keepers(k_list: KeeperList, token: str = Depends(oauth2_scheme)): Keeper.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_keepers)} keepers' + return f"Inserted {len(new_keepers)} keepers" -@router.delete('/{keeper_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{keeper_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_keeper(keeper_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_keeper - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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' + 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') - - + raise HTTPException( + status_code=500, detail=f"Keeper ID {keeper_id} could not be deleted" + ) diff --git a/app/routers_v3/managers.py b/app/routers_v3/managers.py index 739f98f..4c0de88 100644 --- a/app/routers_v3/managers.py +++ b/app/routers_v3/managers.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, Manager, Team, Current, model_to_dict, fn -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/managers', - tags=['managers'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/managers", tags=["managers"]) + class ManagerModel(pydantic.BaseModel): name: str @@ -21,45 +23,45 @@ class ManagerModel(pydantic.BaseModel): bio: Optional[str] = None -@router.get('') +@router.get("") @handle_db_errors async def get_managers( - name: list = Query(default=None), active: Optional[bool] = None, short_output: Optional[bool] = False): + 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')) + ~(Team.abbrev.endswith("IL")) & ~(Team.abbrev.endswith("MiL")) ) - logger.info(f'tquery: {t_query}') + logger.info(f"tquery: {t_query}") a_mgr = [] i_mgr = [] for x in t_query: - logger.info(f'Team: {x.abbrev} / mgr1: {x.manager1} / mgr2: {x.manager2}') + logger.info(f"Team: {x.abbrev} / mgr1: {x.manager1} / mgr2: {x.manager2}") if x.manager1 is not None: a_mgr.append(x.manager1) - logger.info(f'appending {x.manager1.name}') + logger.info(f"appending {x.manager1.name}") if x.manager2 is not None: a_mgr.append(x.manager2) - logger.info(f'appending {x.manager2.name}') + logger.info(f"appending {x.manager2.name}") - logger.info(f'a_mgr: {a_mgr}') + logger.info(f"a_mgr: {a_mgr}") if active: final_mgrs = [model_to_dict(y, recurse=not short_output) for y in a_mgr] else: - logger.info(f'checking inactive') + logger.info(f"checking inactive") for z in Manager.select(): - logger.info(f'checking: {z.name}') + logger.info(f"checking: {z.name}") if z not in a_mgr: - logger.info(f'+inactive: {z.name}') + logger.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 - } + return_managers = {"count": len(final_mgrs), "managers": final_mgrs} else: all_managers = Manager.select() @@ -68,15 +70,17 @@ async def get_managers( 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] + "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}') +@router.get("/{manager_id}") @handle_db_errors async def get_one_manager(manager_id: int, short_output: Optional[bool] = False): this_manager = Manager.get_or_none(Manager.id == manager_id) @@ -85,22 +89,29 @@ async def get_one_manager(manager_id: int, short_output: Optional[bool] = False) db.close() return r_manager else: - raise HTTPException(status_code=404, detail=f'Manager {manager_id} not found') + raise HTTPException(status_code=404, detail=f"Manager {manager_id} not found") -@router.patch('/{manager_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{manager_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors 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)): + 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): - logger.warning(f'patch_manager - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + raise HTTPException( + status_code=404, detail=f"Manager ID {manager_id} not found" + ) if name is not None: this_manager.name = name @@ -117,15 +128,17 @@ async def patch_manager( return r_manager else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch manager {this_manager}') + raise HTTPException( + status_code=500, detail=f"Unable to patch manager {this_manager}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_manager - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_manager - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") this_manager = Manager(**new_manager.dict()) @@ -135,25 +148,31 @@ async def post_manager(new_manager: ManagerModel, token: str = Depends(oauth2_sc return r_manager else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to post manager {this_manager.name}') + raise HTTPException( + status_code=500, detail=f"Unable to post manager {this_manager.name}" + ) -@router.delete('/{manager_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{manager_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_manager(manager_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_manager - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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' + return f"Manager {manager_id} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Manager {manager_id} could not be deleted') + raise HTTPException( + status_code=500, detail=f"Manager {manager_id} could not be deleted" + ) diff --git a/app/routers_v3/pitchingstats.py b/app/routers_v3/pitchingstats.py index 54364ec..cf3357d 100644 --- a/app/routers_v3/pitchingstats.py +++ b/app/routers_v3/pitchingstats.py @@ -6,15 +6,27 @@ 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, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/pitchingstats', - tags=['pitchingstats'] +from ..db_engine import ( + db, + PitchingStat, + Team, + Player, + Current, + model_to_dict, + chunked, + fn, + per_season_weeks, ) +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, +) + +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/pitchingstats", tags=["pitchingstats"]) class PitStatModel(pydantic.BaseModel): @@ -48,29 +60,37 @@ class PitStatList(pydantic.BaseModel): stats: List[PitStatModel] -@router.get('') +@router.get("") @handle_db_errors 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(): + 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']: + 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': []} + return {"count": 0, "stats": []} else: all_stats = PitchingStat.regular_season(season) if all_stats.count() == 0: db.close() - return {'count': 0, 'stats': []} + return {"count": 0, "stats": []} if team_abbrev is not None: t_query = Team.select().where(Team.abbrev << [x.upper() for x in team_abbrev]) @@ -79,7 +99,9 @@ async def get_pitstats( 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]) + 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) @@ -96,7 +118,7 @@ async def get_pitstats( db.close() raise HTTPException( status_code=404, - detail=f'Start week {start} is after end week {end} - cannot pull stats' + detail=f"Start week {start} is after end week {end} - cannot pull stats", ) all_stats = all_stats.where( (PitchingStat.week >= start) & (PitchingStat.week <= end) @@ -105,58 +127,81 @@ async def get_pitstats( if limit: all_stats = all_stats.limit(limit) if sort: - if sort == 'newest': + 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] + "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') +@router.get("/totals") @handle_db_errors 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)): + 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.') + raise HTTPException( + status_code=400, + detail=f"Only one of s_type, week_start/week_end, or week may be used.", + ) # Build SELECT fields conditionally based on group_by to match GROUP BY exactly select_fields = [] - - if group_by == 'player': + + if group_by == "player": select_fields = [PitchingStat.player] - elif group_by == 'team': + elif group_by == "team": select_fields = [PitchingStat.team] - elif group_by == 'playerteam': + elif group_by == "playerteam": select_fields = [PitchingStat.player, PitchingStat.team] else: # Default case select_fields = [PitchingStat.player] all_stats = ( - PitchingStat - .select(*select_fields, - 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'), fn.COUNT(PitchingStat.game).alias('sum_games')) - .where(PitchingStat.season == season) - .having(fn.SUM(PitchingStat.ip) >= ip_min) + PitchingStat.select( + *select_fields, + 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"), + fn.COUNT(PitchingStat.game).alias("sum_games"), + ) + .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]: @@ -166,16 +211,20 @@ async def get_totalstats( 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.' + 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", ) - 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 + weeks["end"] = week_end all_stats = all_stats.where( - (PitchingStat.week >= weeks['start']) & (PitchingStat.week <= weeks['end']) + (PitchingStat.week >= weeks["start"]) & (PitchingStat.week <= weeks["end"]) ) elif week is not None: @@ -189,9 +238,9 @@ async def get_totalstats( if not is_sp: all_stats = all_stats.where(PitchingStat.gs == 0) if sort is not None: - if sort == 'player': + if sort == "player": all_stats = all_stats.order_by(PitchingStat.player) - elif sort == 'team': + elif sort == "team": all_stats = all_stats.order_by(PitchingStat.team) if group_by is not None: # Use the same fields for GROUP BY as we used for SELECT @@ -200,67 +249,76 @@ async def get_totalstats( 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_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_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': [] - } - + return_stats = {"count": all_stats.count(), "stats": []} + for x in all_stats: # Handle player field based on grouping with safe access - this_player = 'TOT' - if 'player' in group_by and hasattr(x, 'player'): - this_player = x.player_id if short_output else model_to_dict(x.player, recurse=False) + this_player = "TOT" + if "player" in group_by and hasattr(x, "player"): + this_player = ( + x.player_id if short_output else model_to_dict(x.player, recurse=False) + ) - # Handle team field based on grouping with safe access - this_team = 'TOT' - if 'team' in group_by and hasattr(x, 'team'): - this_team = x.team_id if short_output else model_to_dict(x.team, recurse=False) - - return_stats['stats'].append({ - 'player': this_player, - 'team': this_team, - '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, - 'games': x.sum_games, - 'win': x.sum_win, - 'loss': x.sum_loss, - 'hold': x.sum_hold, - 'sv': x.sum_sv, - 'bsv': x.sum_bsv - }) + # Handle team field based on grouping with safe access + this_team = "TOT" + if "team" in group_by and hasattr(x, "team"): + this_team = ( + x.team_id if short_output else model_to_dict(x.team, recurse=False) + ) + + return_stats["stats"].append( + { + "player": this_player, + "team": this_team, + "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, + "games": x.sum_games, + "win": x.sum_win, + "loss": x.sum_loss, + "hold": x.sum_hold, + "sv": x.sum_sv, + "bsv": x.sum_bsv, + } + ) db.close() return return_stats -@router.patch('/{stat_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{stat_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors -async def patch_pitstats(stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme)): +async def patch_pitstats( + stat_id: int, new_stats: PitStatModel, token: str = Depends(oauth2_scheme) +): if not valid_token(token): - logger.warning(f'patch_pitstats - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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)) @@ -268,12 +326,12 @@ async def patch_pitstats(stat_id: int, new_stats: PitStatModel, token: str = Dep return r_stat -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_pitstats - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.warning(f"post_pitstats - Bad Token: {token}") + raise HTTPException(status_code=401, detail="Unauthorized") all_stats = [] @@ -281,9 +339,13 @@ async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme) 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') + 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') + raise HTTPException( + status_code=404, detail=f"Player ID {x.player_id} not found" + ) all_stats.append(PitchingStat(**x.dict())) @@ -292,4 +354,4 @@ async def post_pitstats(s_list: PitStatList, token: str = Depends(oauth2_scheme) PitchingStat.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Added {len(all_stats)} batting lines' + return f"Added {len(all_stats)} batting lines" diff --git a/app/routers_v3/players.py b/app/routers_v3/players.py index 5136425..c43e0c2 100644 --- a/app/routers_v3/players.py +++ b/app/routers_v3/players.py @@ -24,8 +24,12 @@ async def get_players( strat_code: list = Query(default=None), is_injured: Optional[bool] = None, sort: Optional[str] = None, - limit: Optional[int] = Query(default=None, ge=1, description="Maximum number of results to return"), - offset: Optional[int] = Query(default=None, ge=0, description="Number of results to skip for pagination"), + limit: Optional[int] = Query( + default=None, ge=1, description="Maximum number of results to return" + ), + offset: Optional[int] = Query( + default=None, ge=0, description="Number of results to skip for pagination" + ), short_output: Optional[bool] = False, csv: Optional[bool] = False, ): @@ -41,9 +45,9 @@ async def get_players( limit=limit, offset=offset, short_output=short_output or False, - as_csv=csv or False + as_csv=csv or False, ) - + if csv: return Response(content=result, media_type="text/csv") return result @@ -54,35 +58,29 @@ async def get_players( @cache_result(ttl=15 * 60, key_prefix="players-search") async def search_players( q: str = Query(..., description="Search query for player name"), - season: Optional[int] = Query(default=None, description="Season to search (0 for all)"), + season: Optional[int] = Query( + default=None, description="Season to search (0 for all)" + ), limit: int = Query(default=10, ge=1, le=50), short_output: bool = False, ): """Search players by name with fuzzy matching.""" return PlayerService.search_players( - query_str=q, - season=season, - limit=limit, - short_output=short_output + query_str=q, season=season, limit=limit, short_output=short_output ) @router.get("/{player_id}") @handle_db_errors @cache_result(ttl=30 * 60, key_prefix="player") -async def get_one_player( - player_id: int, - short_output: Optional[bool] = False -): +async def get_one_player(player_id: int, short_output: Optional[bool] = False): """Get a single player by ID.""" return PlayerService.get_player(player_id, short_output=short_output or False) @router.put("/{player_id}") async def put_player( - player_id: int, - new_player: dict, - token: str = Depends(oauth2_scheme) + player_id: int, new_player: dict, token: str = Depends(oauth2_scheme) ): """Update a player (full replacement).""" return PlayerService.update_player(player_id, new_player, token) @@ -120,14 +118,28 @@ async def patch_player( data = { key: value for key, value in { - 'name': name, 'wara': wara, 'image': image, 'image2': image2, - 'team_id': team_id, 'season': season, - 'pos_1': pos_1, 'pos_2': pos_2, 'pos_3': pos_3, 'pos_4': pos_4, - 'pos_5': pos_5, 'pos_6': pos_6, 'pos_7': pos_7, 'pos_8': pos_8, - 'vanity_card': vanity_card, 'headshot': headshot, - 'il_return': il_return, 'demotion_week': demotion_week, - 'strat_code': strat_code, 'bbref_id': bbref_id, - 'injury_rating': injury_rating, 'sbaref_id': sbaref_id, + "name": name, + "wara": wara, + "image": image, + "image2": image2, + "team_id": team_id, + "season": season, + "pos_1": pos_1, + "pos_2": pos_2, + "pos_3": pos_3, + "pos_4": pos_4, + "pos_5": pos_5, + "pos_6": pos_6, + "pos_7": pos_7, + "pos_8": pos_8, + "vanity_card": vanity_card, + "headshot": headshot, + "il_return": il_return, + "demotion_week": demotion_week, + "strat_code": strat_code, + "bbref_id": bbref_id, + "injury_rating": injury_rating, + "sbaref_id": sbaref_id, }.items() if value is not None } @@ -135,19 +147,13 @@ async def patch_player( return PlayerService.patch_player(player_id, data, token) -@router.post("") -async def post_players( - p_list: dict, - token: str = Depends(oauth2_scheme) -): +@router.post("/") +async def post_players(p_list: dict, token: str = Depends(oauth2_scheme)): """Create multiple players.""" return PlayerService.create_players(p_list.get("players", []), token) @router.delete("/{player_id}") -async def delete_player( - player_id: int, - token: str = Depends(oauth2_scheme) -): +async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): """Delete a player.""" return PlayerService.delete_player(player_id, token) diff --git a/app/routers_v3/results.py b/app/routers_v3/results.py index 53cc085..76bd440 100644 --- a/app/routers_v3/results.py +++ b/app/routers_v3/results.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, Result, Team, model_to_dict, chunked -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/results', - tags=['results'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/results", tags=["results"]) + class ResultModel(pydantic.BaseModel): week: int @@ -29,13 +31,18 @@ class ResultList(pydantic.BaseModel): results: List[ResultModel] -@router.get('') +@router.get("") @handle_db_errors 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): + 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: @@ -68,14 +75,14 @@ async def get_results( 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] + "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}') +@router.get("/{result_id}") @handle_db_errors async def get_one_result(result_id: int, short_output: Optional[bool] = False): this_result = Result.get_or_none(Result.id == result_id) @@ -87,20 +94,27 @@ async def get_one_result(result_id: int, short_output: Optional[bool] = False): return r_result -@router.patch('/{result_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{result_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors 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)): + 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): - logger.warning(f'patch_player - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + raise HTTPException(status_code=404, detail=f"Result ID {result_id} not found") if week_num is not None: this_result.week = week_num @@ -132,22 +146,28 @@ async def patch_result( return r_result else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch result {result_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch result {result_id}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_results(result_list: ResultList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'patch_player - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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') + raise HTTPException( + status_code=404, detail=f"Team ID {x.hometeam_id} not found" + ) new_results.append(x.dict()) @@ -156,27 +176,27 @@ async def post_results(result_list: ResultList, token: str = Depends(oauth2_sche Result.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_results)} results' + return f"Inserted {len(new_results)} results" -@router.delete('/{result_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{result_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_result(result_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_result - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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' + return f"Result {result_id} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Result {result_id} could not be deleted') - - + raise HTTPException( + status_code=500, detail=f"Result {result_id} could not be deleted" + ) diff --git a/app/routers_v3/sbaplayers.py b/app/routers_v3/sbaplayers.py index 439081a..296e21e 100644 --- a/app/routers_v3/sbaplayers.py +++ b/app/routers_v3/sbaplayers.py @@ -7,15 +7,17 @@ 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, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/sbaplayers', - tags=['sbaplayers'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/sbaplayers", tags=["sbaplayers"]) + class SbaPlayerModel(pydantic.BaseModel): first_name: str @@ -30,19 +32,26 @@ class PlayerList(pydantic.BaseModel): players: List[SbaPlayerModel] -@router.get('') +@router.get("") @handle_db_errors 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): + 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 + 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] @@ -60,76 +69,87 @@ async def get_players( 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']: + if sort in ["firstname-asc", "fullname-asc"]: all_players = all_players.order_by(SbaPlayer.first_name) - elif sort in ['firstname-desc', 'fullname-desc']: + elif sort in ["firstname-desc", "fullname-desc"]: all_players = all_players.order_by(-SbaPlayer.first_name) - elif sort == 'lastname-asc': + elif sort == "lastname-asc": all_players = all_players.order_by(SbaPlayer.last_name) - elif sort == 'lastname-desc': + elif sort == "lastname-desc": all_players = all_players.order_by(-SbaPlayer.last_name) - elif sort == 'fangraphs-asc': + elif sort == "fangraphs-asc": all_players = all_players.order_by(SbaPlayer.key_fangraphs) - elif sort == 'fangraphs-desc': + elif sort == "fangraphs-desc": all_players = all_players.order_by(-SbaPlayer.key_fangraphs) - elif sort == 'bbref-asc': + elif sort == "bbref-asc": all_players = all_players.order_by(SbaPlayer.key_bbref) - elif sort == 'bbref-desc': + elif sort == "bbref-desc": all_players = all_players.order_by(-SbaPlayer.key_bbref) - elif sort == 'retro-asc': + elif sort == "retro-asc": all_players = all_players.order_by(SbaPlayer.key_retro) - elif sort == 'retro-desc': + elif sort == "retro-desc": all_players = all_players.order_by(-SbaPlayer.key_retro) - elif sort == 'mlbam-asc': + elif sort == "mlbam-asc": all_players = all_players.order_by(SbaPlayer.key_mlbam) - elif sort == 'mlbam-desc': + 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 Response(content=return_val, media_type="text/csv") - return_val = {'count': all_players.count(), 'players': [ - model_to_dict(x) for x in all_players - ]} + 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}') +@router.get("/{player_id}") @handle_db_errors 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') + 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) +@router.patch("/{player_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors 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)): + 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}') + 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.' + 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') + 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 @@ -152,33 +172,38 @@ async def patch_player( db.close() raise HTTPException( status_code=418, - detail='Well slap my ass and call me a teapot; I could not save that player' + detail="Well slap my ass and call me a teapot; I could not save that player", ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning(f'Bad 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.' + 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) & (x.key_fangraphs is not None)) | ((SbaPlayer.key_mlbam == x.key_mlbam) & (x.key_mlbam is not None)) | - ((SbaPlayer.key_retro == x.key_retro) & (x.key_retro is not None)) | ((SbaPlayer.key_bbref == x.key_bbref) & (x.key_bbref is not None)) + ( + (SbaPlayer.key_fangraphs == x.key_fangraphs) + & (x.key_fangraphs is not None) + ) + | ((SbaPlayer.key_mlbam == x.key_mlbam) & (x.key_mlbam is not None)) + | ((SbaPlayer.key_retro == x.key_retro) & (x.key_retro is not None)) + | ((SbaPlayer.key_bbref == x.key_bbref) & (x.key_bbref is not None)) ) if dupes.count() > 0: - logger.error(f'Found a dupe for {x}') + logger.error(f"Found a dupe for {x}") db.close() raise HTTPException( status_code=400, - detail=f'{x.first_name} {x.last_name} has a key already in the database' + detail=f"{x.first_name} {x.last_name} has a key already in the database", ) new_players.append(x.model_dump()) @@ -188,32 +213,33 @@ async def post_players(players: PlayerList, token: str = Depends(oauth2_scheme)) SbaPlayer.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_players)} new MLB players' + return f"Inserted {len(new_players)} new MLB players" -@router.post('/one', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/one", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning(f'Bad 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.' + 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) + (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:') + logging.info(f"POST /SbaPlayers/one - dupes found:") for x in dupes: - logging.info(f'{x}') + 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' + detail=f"{player.first_name} {player.last_name} has a key already in the database", ) new_player = SbaPlayer(**player.dict()) @@ -226,30 +252,34 @@ async def post_one_player(player: SbaPlayerModel, token: str = Depends(oauth2_sc db.close() raise HTTPException( status_code=418, - detail='Well slap my ass and call me a teapot; I could not save that player' + 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) +@router.delete("/{player_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_player(player_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logging.warning(f'Bad 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.' + 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') + raise HTTPException( + status_code=404, detail=f"SbaPlayer id {player_id} not found" + ) count = this_player.delete_instance() db.close() if count == 1: - return f'Player {player_id} has been deleted' + return 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 + raise HTTPException( + status_code=500, detail=f"Player {player_id} was not deleted" + ) diff --git a/app/routers_v3/schedules.py b/app/routers_v3/schedules.py index 5b76a67..a7bca82 100644 --- a/app/routers_v3/schedules.py +++ b/app/routers_v3/schedules.py @@ -4,15 +4,17 @@ import logging import pydantic from ..db_engine import db, Schedule, Team, model_to_dict, chunked -from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors - -logger = logging.getLogger('discord_app') - -router = APIRouter( - prefix='/api/v3/schedules', - tags=['schedules'] +from ..dependencies import ( + oauth2_scheme, + valid_token, + PRIVATE_IN_SCHEMA, + handle_db_errors, ) +logger = logging.getLogger("discord_app") + +router = APIRouter(prefix="/api/v3/schedules", tags=["schedules"]) + class ScheduleModel(pydantic.BaseModel): week: int @@ -26,12 +28,17 @@ class ScheduleList(pydantic.BaseModel): schedules: List[ScheduleModel] -@router.get('') +@router.get("") @handle_db_errors 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): + 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: @@ -63,14 +70,14 @@ async def get_schedules( 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] + "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}') +@router.get("/{schedule_id}") @handle_db_errors async def get_one_schedule(schedule_id: int): this_sched = Schedule.get_or_none(Schedule.id == schedule_id) @@ -82,19 +89,26 @@ async def get_one_schedule(schedule_id: int): return r_sched -@router.patch('/{schedule_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.patch("/{schedule_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors 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)): + 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): - logger.warning(f'patch_schedule - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + raise HTTPException( + status_code=404, detail=f"Schedule ID {schedule_id} not found" + ) if week is not None: this_sched.week = week @@ -117,22 +131,28 @@ async def patch_schedule( return r_sched else: db.close() - raise HTTPException(status_code=500, detail=f'Unable to patch schedule {schedule_id}') + raise HTTPException( + status_code=500, detail=f"Unable to patch schedule {schedule_id}" + ) -@router.post('', include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'post_schedules - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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') + raise HTTPException( + status_code=404, detail=f"Team ID {x.hometeam_id} not found" + ) new_sched.append(x.dict()) @@ -141,24 +161,28 @@ async def post_schedules(sched_list: ScheduleList, token: str = Depends(oauth2_s Schedule.insert_many(batch).on_conflict_ignore().execute() db.close() - return f'Inserted {len(new_sched)} schedules' + return f"Inserted {len(new_sched)} schedules" -@router.delete('/{schedule_id}', include_in_schema=PRIVATE_IN_SCHEMA) +@router.delete("/{schedule_id}", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def delete_schedule(schedule_id: int, token: str = Depends(oauth2_scheme)): if not valid_token(token): - logger.warning(f'delete_schedule - Bad Token: {token}') - raise HTTPException(status_code=401, detail='Unauthorized') + logger.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') + 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' + return f"Schedule {this_sched} has been deleted" else: - raise HTTPException(status_code=500, detail=f'Schedule {this_sched} could not be deleted') + raise HTTPException( + status_code=500, detail=f"Schedule {this_sched} could not be deleted" + ) diff --git a/app/routers_v3/stratgame.py b/app/routers_v3/stratgame.py index 8d26799..ba750a8 100644 --- a/app/routers_v3/stratgame.py +++ b/app/routers_v3/stratgame.py @@ -230,7 +230,7 @@ async def patch_game( raise HTTPException(status_code=500, detail=f"Unable to patch game {game_id}") -@router.post("", include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_games(game_list: GameList, token: str = Depends(oauth2_scheme)) -> Any: if not valid_token(token): diff --git a/app/routers_v3/teams.py b/app/routers_v3/teams.py index f64d6a1..b245653 100644 --- a/app/routers_v3/teams.py +++ b/app/routers_v3/teams.py @@ -6,16 +6,21 @@ Thin HTTP layer using TeamService for business logic. from fastapi import APIRouter, Query, Response, Depends from typing import List, Optional, Literal -from ..dependencies import oauth2_scheme, PRIVATE_IN_SCHEMA, handle_db_errors, cache_result +from ..dependencies import ( + oauth2_scheme, + PRIVATE_IN_SCHEMA, + handle_db_errors, + cache_result, +) from ..services.base import BaseService from ..services.team_service import TeamService -router = APIRouter(prefix='/api/v3/teams', tags=['teams']) +router = APIRouter(prefix="/api/v3/teams", tags=["teams"]) -@router.get('') +@router.get("") @handle_db_errors -@cache_result(ttl=10*60, key_prefix='teams') +@cache_result(ttl=10 * 60, key_prefix="teams") async def get_teams( season: Optional[int] = None, owner_id: list = Query(default=None), @@ -23,7 +28,7 @@ async def get_teams( team_abbrev: list = Query(default=None), active_only: Optional[bool] = False, short_output: Optional[bool] = False, - csv: Optional[bool] = False + csv: Optional[bool] = False, ): """Get teams with filtering.""" result = TeamService.get_teams( @@ -33,46 +38,41 @@ async def get_teams( team_abbrev=team_abbrev if team_abbrev else None, active_only=active_only or False, short_output=short_output or False, - as_csv=csv or False + as_csv=csv or False, ) - + if csv: - return Response(content=result, media_type='text/csv') + return Response(content=result, media_type="text/csv") return result -@router.get('/{team_id}') +@router.get("/{team_id}") @handle_db_errors -@cache_result(ttl=30*60, key_prefix='team') +@cache_result(ttl=30 * 60, key_prefix="team") async def get_one_team(team_id: int): """Get a single team by ID.""" return TeamService.get_team(team_id) -@router.get('/{team_id}/roster') +@router.get("/{team_id}/roster") @handle_db_errors -@cache_result(ttl=30*60, key_prefix='team-roster') -async def get_team_roster_default( - team_id: int, - sort: Optional[str] = None -): +@cache_result(ttl=30 * 60, key_prefix="team-roster") +async def get_team_roster_default(team_id: int, sort: Optional[str] = None): """Get team roster with IL lists (defaults to current season).""" - return TeamService.get_team_roster(team_id, 'current', sort=sort) + return TeamService.get_team_roster(team_id, "current", sort=sort) -@router.get('/{team_id}/roster/{which}') +@router.get("/{team_id}/roster/{which}") @handle_db_errors -@cache_result(ttl=30*60, key_prefix='team-roster') +@cache_result(ttl=30 * 60, key_prefix="team-roster") async def get_team_roster( - team_id: int, - which: Literal['current', 'next'], - sort: Optional[str] = None + team_id: int, which: Literal["current", "next"], sort: Optional[str] = None ): """Get team roster with IL lists.""" return TeamService.get_team_roster(team_id, which, sort=sort) -@router.patch('/{team_id}') +@router.patch("/{team_id}") async def patch_team( team_id: int, token: str = Depends(oauth2_scheme), @@ -95,31 +95,33 @@ async def patch_team( data = { key: value for key, value in { - 'manager1_id': manager1_id, 'manager2_id': manager2_id, - 'gmid': gmid, 'gmid2': gmid2, 'mascot': mascot, - 'stadium': stadium, 'thumbnail': thumbnail, 'color': color, - 'abbrev': abbrev, 'sname': sname, 'lname': lname, - 'dice_color': dice_color, 'division_id': division_id, + "manager1_id": manager1_id, + "manager2_id": manager2_id, + "gmid": gmid, + "gmid2": gmid2, + "mascot": mascot, + "stadium": stadium, + "thumbnail": thumbnail, + "color": color, + "abbrev": abbrev, + "sname": sname, + "lname": lname, + "dice_color": dice_color, + "division_id": division_id, }.items() if value is not None } - + return TeamService.update_team(team_id, data, token) -@router.post('') -async def post_teams( - team_list: dict, - token: str = Depends(oauth2_scheme) -): +@router.post("/") +async def post_teams(team_list: dict, token: str = Depends(oauth2_scheme)): """Create multiple teams.""" return TeamService.create_teams(team_list.get("teams", []), token) -@router.delete('/{team_id}') -async def delete_team( - team_id: int, - token: str = Depends(oauth2_scheme) -): +@router.delete("/{team_id}") +async def delete_team(team_id: int, token: str = Depends(oauth2_scheme)): """Delete a team.""" return TeamService.delete_team(team_id, token) diff --git a/app/routers_v3/transactions.py b/app/routers_v3/transactions.py index 4aa59f7..4d11e3a 100644 --- a/app/routers_v3/transactions.py +++ b/app/routers_v3/transactions.py @@ -132,7 +132,7 @@ async def patch_transactions( return f"Updated {these_moves.count()} transactions" -@router.post("", include_in_schema=PRIVATE_IN_SCHEMA) +@router.post("/", include_in_schema=PRIVATE_IN_SCHEMA) @handle_db_errors async def post_transactions( moves: TransactionList, token: str = Depends(oauth2_scheme) -- 2.25.1