Add Redis Caching
This commit is contained in:
parent
abf4435931
commit
a540a3e7f3
@ -5,7 +5,7 @@ import pydantic
|
|||||||
from pandas import DataFrame
|
from pandas import DataFrame
|
||||||
|
|
||||||
from ..db_engine import db, Player, model_to_dict, chunked, fn, complex_data_to_csv
|
from ..db_engine import db, Player, model_to_dict, chunked, fn, complex_data_to_csv
|
||||||
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors
|
from ..dependencies import add_cache_headers, cache_result, oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors
|
||||||
|
|
||||||
logger = logging.getLogger('discord_app')
|
logger = logging.getLogger('discord_app')
|
||||||
|
|
||||||
@ -49,6 +49,7 @@ class PlayerList(pydantic.BaseModel):
|
|||||||
|
|
||||||
@router.get('')
|
@router.get('')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
async def get_players(
|
async def get_players(
|
||||||
season: Optional[int], name: Optional[str] = None, team_id: list = Query(default=None),
|
season: Optional[int], name: Optional[str] = None, team_id: list = Query(default=None),
|
||||||
pos: list = Query(default=None), strat_code: list = Query(default=None), is_injured: Optional[bool] = None,
|
pos: list = Query(default=None), strat_code: list = Query(default=None), is_injured: Optional[bool] = None,
|
||||||
@ -84,6 +85,8 @@ async def get_players(
|
|||||||
all_players = all_players.order_by(Player.name)
|
all_players = all_players.order_by(Player.name)
|
||||||
elif sort == 'name-desc':
|
elif sort == 'name-desc':
|
||||||
all_players = all_players.order_by(-Player.name)
|
all_players = all_players.order_by(-Player.name)
|
||||||
|
else:
|
||||||
|
all_players = all_players.order_by(Player.id)
|
||||||
|
|
||||||
if csv:
|
if csv:
|
||||||
player_list = [
|
player_list = [
|
||||||
@ -123,6 +126,7 @@ async def get_players(
|
|||||||
|
|
||||||
@router.get('/{player_id}')
|
@router.get('/{player_id}')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
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):
|
||||||
this_player = Player.get_or_none(Player.id == player_id)
|
this_player = Player.get_or_none(Player.id == player_id)
|
||||||
if this_player:
|
if this_player:
|
||||||
|
|||||||
@ -9,7 +9,7 @@ from pydantic import BaseModel, validator
|
|||||||
|
|
||||||
from ..db_engine import db, StratPlay, StratGame, Team, Player, Decision, model_to_dict, chunked, fn, SQL, \
|
from ..db_engine import db, StratPlay, StratGame, Team, Player, Decision, model_to_dict, chunked, fn, SQL, \
|
||||||
complex_data_to_csv
|
complex_data_to_csv
|
||||||
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors
|
from ..dependencies import add_cache_headers, cache_result, oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors
|
||||||
|
|
||||||
logger = logging.getLogger('discord_app')
|
logger = logging.getLogger('discord_app')
|
||||||
|
|
||||||
@ -122,6 +122,8 @@ class PlayList(BaseModel):
|
|||||||
|
|
||||||
@router.get('')
|
@router.get('')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
|
@cache_result(ttl=5*60, key_prefix='plays')
|
||||||
async def get_plays(
|
async def get_plays(
|
||||||
game_id: list = Query(default=None), batter_id: list = Query(default=None), season: list = Query(default=None),
|
game_id: list = Query(default=None), batter_id: list = Query(default=None), season: list = Query(default=None),
|
||||||
week: list = Query(default=None), has_defender: Optional[bool] = None, has_catcher: Optional[bool] = None,
|
week: list = Query(default=None), has_defender: Optional[bool] = None, has_catcher: Optional[bool] = None,
|
||||||
@ -278,6 +280,8 @@ async def get_plays(
|
|||||||
|
|
||||||
@router.get('/batting')
|
@router.get('/batting')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
|
@cache_result(ttl=5*60, key_prefix='plays-batting')
|
||||||
async def get_batting_totals(
|
async def get_batting_totals(
|
||||||
season: list = Query(default=None), week: list = Query(default=None),
|
season: list = Query(default=None), week: list = Query(default=None),
|
||||||
s_type: Literal['regular', 'post', 'total', None] = None, position: list = Query(default=None),
|
s_type: Literal['regular', 'post', 'total', None] = None, position: list = Query(default=None),
|
||||||
@ -691,6 +695,8 @@ async def get_batting_totals(
|
|||||||
|
|
||||||
@router.get('/pitching')
|
@router.get('/pitching')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
|
@cache_result(ttl=5*60, key_prefix='plays-batting')
|
||||||
async def get_pitching_totals(
|
async def get_pitching_totals(
|
||||||
season: list = Query(default=None), week: list = Query(default=None),
|
season: list = Query(default=None), week: list = Query(default=None),
|
||||||
s_type: Literal['regular', 'post', 'total', None] = None, player_id: list = Query(default=None),
|
s_type: Literal['regular', 'post', 'total', None] = None, player_id: list = Query(default=None),
|
||||||
@ -942,6 +948,8 @@ async def get_pitching_totals(
|
|||||||
|
|
||||||
@router.get('/fielding')
|
@router.get('/fielding')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
|
@cache_result(ttl=5*60, key_prefix='plays-fielding')
|
||||||
async def get_fielding_totals(
|
async def get_fielding_totals(
|
||||||
season: list = Query(default=None), week: list = Query(default=None),
|
season: list = Query(default=None), week: list = Query(default=None),
|
||||||
s_type: Literal['regular', 'post', 'total', None] = None, position: list = Query(default=None),
|
s_type: Literal['regular', 'post', 'total', None] = None, position: list = Query(default=None),
|
||||||
|
|||||||
@ -5,7 +5,7 @@ import logging
|
|||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
from ..db_engine import db, Team, Manager, Division, model_to_dict, chunked, fn, query_to_csv, Player
|
from ..db_engine import db, Team, Manager, Division, model_to_dict, chunked, fn, query_to_csv, Player
|
||||||
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors
|
from ..dependencies import add_cache_headers, cache_result, oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors
|
||||||
|
|
||||||
logger = logging.getLogger('discord_app')
|
logger = logging.getLogger('discord_app')
|
||||||
|
|
||||||
@ -37,6 +37,7 @@ class TeamList(pydantic.BaseModel):
|
|||||||
|
|
||||||
@router.get('')
|
@router.get('')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@cache_result(ttl=10*60, key_prefix='teams')
|
||||||
async def get_teams(
|
async def get_teams(
|
||||||
season: Optional[int] = None, owner_id: list = Query(default=None), manager_id: list = Query(default=None),
|
season: Optional[int] = None, owner_id: list = Query(default=None), manager_id: list = Query(default=None),
|
||||||
team_abbrev: list = Query(default=None), active_only: Optional[bool] = False,
|
team_abbrev: list = Query(default=None), active_only: Optional[bool] = False,
|
||||||
@ -76,6 +77,8 @@ async def get_teams(
|
|||||||
|
|
||||||
@router.get('/{team_id}')
|
@router.get('/{team_id}')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=60*60)
|
||||||
|
@cache_result(ttl=30*60, key_prefix='team')
|
||||||
async def get_one_team(team_id: int):
|
async def get_one_team(team_id: int):
|
||||||
this_team = Team.get_or_none(Team.id == team_id)
|
this_team = Team.get_or_none(Team.id == team_id)
|
||||||
if this_team:
|
if this_team:
|
||||||
@ -88,6 +91,8 @@ async def get_one_team(team_id: int):
|
|||||||
|
|
||||||
@router.get('/{team_id}/roster/{which}', include_in_schema=PRIVATE_IN_SCHEMA)
|
@router.get('/{team_id}/roster/{which}', include_in_schema=PRIVATE_IN_SCHEMA)
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=60*60)
|
||||||
|
@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):
|
async def get_team_roster(team_id: int, which: Literal['current', 'next'], sort: Optional[str] = None):
|
||||||
try:
|
try:
|
||||||
this_team = Team.get_by_id(team_id)
|
this_team = Team.get_by_id(team_id)
|
||||||
|
|||||||
@ -4,7 +4,7 @@ import logging
|
|||||||
import pydantic
|
import pydantic
|
||||||
|
|
||||||
from ..db_engine import SeasonBattingStats, SeasonPitchingStats, db, Manager, Team, Current, model_to_dict, fn, query_to_csv, StratPlay, StratGame
|
from ..db_engine import SeasonBattingStats, SeasonPitchingStats, db, Manager, Team, Current, model_to_dict, fn, query_to_csv, StratPlay, StratGame
|
||||||
from ..dependencies import oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors, update_season_batting_stats, update_season_pitching_stats
|
from ..dependencies import add_cache_headers, cache_result, oauth2_scheme, valid_token, PRIVATE_IN_SCHEMA, handle_db_errors, update_season_batting_stats, update_season_pitching_stats, get_cache_stats
|
||||||
|
|
||||||
logger = logging.getLogger('discord_app')
|
logger = logging.getLogger('discord_app')
|
||||||
|
|
||||||
@ -15,6 +15,8 @@ router = APIRouter(
|
|||||||
|
|
||||||
@router.get('/season-stats/batting')
|
@router.get('/season-stats/batting')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
|
@cache_result(ttl=5*60, key_prefix='season-batting')
|
||||||
async def get_season_batting_stats(
|
async def get_season_batting_stats(
|
||||||
season: Optional[int] = None,
|
season: Optional[int] = None,
|
||||||
team_id: Optional[int] = None,
|
team_id: Optional[int] = None,
|
||||||
@ -109,6 +111,8 @@ async def refresh_season_batting_stats(
|
|||||||
|
|
||||||
@router.get('/season-stats/pitching')
|
@router.get('/season-stats/pitching')
|
||||||
@handle_db_errors
|
@handle_db_errors
|
||||||
|
@add_cache_headers(max_age=10*60)
|
||||||
|
@cache_result(ttl=5*60, key_prefix='season-pitching')
|
||||||
async def get_season_pitching_stats(
|
async def get_season_pitching_stats(
|
||||||
season: Optional[int] = None,
|
season: Optional[int] = None,
|
||||||
team_id: Optional[int] = None,
|
team_id: Optional[int] = None,
|
||||||
@ -207,3 +211,31 @@ async def refresh_season_pitching_stats(
|
|||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.error(f'Error refreshing season {season} pitching stats: {e}')
|
logger.error(f'Error refreshing season {season} pitching stats: {e}')
|
||||||
raise HTTPException(status_code=500, detail=f'Refresh failed: {str(e)}')
|
raise HTTPException(status_code=500, detail=f'Refresh failed: {str(e)}')
|
||||||
|
|
||||||
|
|
||||||
|
@router.get('/admin/cache', include_in_schema=PRIVATE_IN_SCHEMA)
|
||||||
|
@handle_db_errors
|
||||||
|
async def get_admin_cache_stats(
|
||||||
|
token: str = Depends(oauth2_scheme)
|
||||||
|
) -> dict:
|
||||||
|
"""
|
||||||
|
Get Redis cache statistics and status.
|
||||||
|
Private endpoint - requires authentication.
|
||||||
|
"""
|
||||||
|
if not valid_token(token):
|
||||||
|
logger.warning(f'get_admin_cache_stats - Bad Token: {token}')
|
||||||
|
raise HTTPException(status_code=401, detail='Unauthorized')
|
||||||
|
|
||||||
|
logger.info('Getting cache statistics')
|
||||||
|
|
||||||
|
try:
|
||||||
|
cache_stats = get_cache_stats()
|
||||||
|
logger.info(f'Cache stats retrieved: {cache_stats}')
|
||||||
|
return {
|
||||||
|
'status': 'success',
|
||||||
|
'cache_info': cache_stats
|
||||||
|
}
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logger.error(f'Error getting cache stats: {e}')
|
||||||
|
raise HTTPException(status_code=500, detail=f'Failed to get cache stats: {str(e)}')
|
||||||
|
|||||||
@ -31,8 +31,12 @@ services:
|
|||||||
- POSTGRES_DB=${SBA_DATABASE}
|
- POSTGRES_DB=${SBA_DATABASE}
|
||||||
- POSTGRES_USER=${SBA_DB_USER}
|
- POSTGRES_USER=${SBA_DB_USER}
|
||||||
- POSTGRES_PASSWORD=${SBA_DB_USER_PASSWORD}
|
- POSTGRES_PASSWORD=${SBA_DB_USER_PASSWORD}
|
||||||
|
- REDIS_HOST=sba_redis
|
||||||
|
- REDIS_PORT=6379
|
||||||
|
- REDIS_DB=0
|
||||||
depends_on:
|
depends_on:
|
||||||
- postgres
|
- postgres
|
||||||
|
- redis
|
||||||
|
|
||||||
postgres:
|
postgres:
|
||||||
image: postgres:17-alpine
|
image: postgres:17-alpine
|
||||||
@ -55,6 +59,24 @@ services:
|
|||||||
retries: 3
|
retries: 3
|
||||||
start_period: 30s
|
start_period: 30s
|
||||||
|
|
||||||
|
redis:
|
||||||
|
image: redis:7-alpine
|
||||||
|
restart: unless-stopped
|
||||||
|
container_name: sba_redis
|
||||||
|
ports:
|
||||||
|
- "6379:6379"
|
||||||
|
volumes:
|
||||||
|
- redis_data:/data
|
||||||
|
environment:
|
||||||
|
- TZ=${TZ}
|
||||||
|
healthcheck:
|
||||||
|
test: ["CMD", "redis-cli", "ping"]
|
||||||
|
interval: 30s
|
||||||
|
timeout: 10s
|
||||||
|
retries: 3
|
||||||
|
start_period: 30s
|
||||||
|
command: redis-server --appendonly yes
|
||||||
|
|
||||||
adminer:
|
adminer:
|
||||||
image: adminer:latest
|
image: adminer:latest
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
@ -94,3 +116,4 @@ services:
|
|||||||
|
|
||||||
volumes:
|
volumes:
|
||||||
postgres_data:
|
postgres_data:
|
||||||
|
redis_data:
|
||||||
@ -5,3 +5,4 @@ python-multipart
|
|||||||
pandas
|
pandas
|
||||||
psycopg2-binary>=2.9.0
|
psycopg2-binary>=2.9.0
|
||||||
requests
|
requests
|
||||||
|
redis>=4.5.0
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user