Update cache refresh logic to replace vs delete

This commit is contained in:
Cal Corum 2025-02-23 22:50:58 -06:00
parent 557404301a
commit f8aad38739
6 changed files with 225 additions and 86 deletions

View File

@ -3,6 +3,22 @@ from typing import Literal
logger = logging.getLogger('discord_app')
def log_errors(func):
"""
This wrapper function will force all exceptions to be logged with executiona and stack info.
"""
def wrap(*args, **kwargs):
try:
result = func(*args, **kwargs)
except Exception as e:
log_exception(e)
logger.info(func.__name__)
return result
return wrap
def log_exception(e: Exception, msg: str = '', level: Literal['debug', 'error', 'info', 'warn'] = 'error'):
if level == 'debug':
logger.debug(msg, exc_info=True, stack_info=True)

View File

@ -2438,7 +2438,7 @@ async def give_packs(team: dict, num_packs: int, pack_type: dict = None) -> dict
def get_sheets(bot):
try:
return bot.get_cog('Players').sheets
return bot.get_cog('Gameplay').sheets
except Exception as e:
logger.error(f'Could not grab sheets auth: {e}')
raise ConnectionError(f'Bot has not authenticated with discord; please try again in 1 minute.')

View File

@ -19,7 +19,8 @@ logger = logging.getLogger('discord_app')
# sqlite_url = 'sqlite:///storage/gameplay.db'
# connect_args = {"check_same_thread": False}
# engine = create_engine(sqlite_url, echo=False, connect_args=connect_args)
engine = create_engine(f'postgresql://{os.getenv('DB_USERNAME')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_URL')}/{os.getenv('DB_NAME')}')
postgres_url = f'postgresql://{os.getenv('DB_USERNAME')}:{os.getenv('DB_PASSWORD')}@{os.getenv('DB_URL')}/{os.getenv('DB_NAME')}'
engine = create_engine(postgres_url)
CACHE_LIMIT = 1209600 # in seconds
SBA_COLOR = 'a6ce39'
SBA_LOGO = 'https://sombaseball.ddns.net/static/images/sba-logo.png'

View File

@ -8,7 +8,7 @@ from sqlalchemy import func
from api_calls import db_get, db_post
from sqlmodel import col
from in_game.gameplay_models import CACHE_LIMIT, BatterScouting, BatterScoutingBase, BattingCard, BattingCardBase, BattingRatings, BattingRatingsBase, Card, CardBase, Cardset, CardsetBase, GameCardsetLink, Lineup, PitcherScouting, PitchingCard, PitchingCardBase, PitchingRatings, PitchingRatingsBase, Player, PlayerBase, PositionRating, PositionRatingBase, RosterLink, Session, Team, TeamBase, select, or_, Game, Play
from exceptions import DatabaseError, PositionNotFoundException, log_exception, PlayNotFoundException
from exceptions import DatabaseError, PositionNotFoundException, log_errors, log_exception, PlayNotFoundException
logger = logging.getLogger('discord_app')
@ -93,14 +93,14 @@ async def get_team_or_none(
this_team = session.exec(select(Team).where(func.lower(Team.abbrev) == team_abbrev.lower())).one_or_none()
if this_team is not None:
logger.debug(f'we found a team: {this_team} / created: {this_team.created}')
logger.info(f'we found a team: {this_team} / created: {this_team.created}')
tdelta = datetime.datetime.now() - this_team.created
logger.debug(f'tdelta: {tdelta}')
logger.info(f'tdelta: {tdelta}')
if tdelta.total_seconds() < CACHE_LIMIT:
return this_team
else:
session.delete(this_team)
session.commit()
# else:
# session.delete(this_team)
# session.commit()
def cache_team(json_data: dict) -> Team:
logger.info(f'gameplay_queries - cache_team - writing a team to cache: {json_data}')
@ -108,6 +108,24 @@ async def get_team_or_none(
logger.info(f'gameplay_queries - cache_team - valid_team: {valid_team}')
db_team = Team.model_validate(valid_team)
logger.info(f'gameplay_queries - cache_team - db_team: {db_team}')
logger.info(f'Checking for existing team ID: {db_team.id}')
try:
this_team = session.exec(select(Team).where(Team.id == db_team.id)).one()
logger.info(f'Found team: {this_team}\nUpdating with db team: {db_team}')
for key, value in db_team.model_dump(exclude_unset=True).items():
logger.info(f'Setting key ({key}) to value ({value})')
setattr(this_team, key, value)
logger.info(f'Set this_team to db_team')
session.add(this_team)
session.commit()
logger.info(f'Refreshing this_team')
session.refresh(this_team)
return this_team
except:
logger.info(f'Team not found, adding to db')
session.add(db_team)
session.commit()
session.refresh(db_team)
@ -193,15 +211,31 @@ async def get_player_or_none(session: Session, player_id: int, skip_cache: bool
if tdelta.total_seconds() < CACHE_LIMIT:
logger.info(f'returning this player')
return this_player
else:
logger.warning('Deleting old player record')
session.delete(this_player)
session.commit()
# else:
# logger.warning('Deleting old player record')
# session.delete(this_player)
# session.commit()
def cache_player(json_data: dict) -> Player:
logger.info(f'gameplay_models - get_player_or_none - cache_player - caching player data: {json_data}')
valid_player = PlayerBase.model_validate(json_data, from_attributes=True)
db_player = Player.model_validate(valid_player)
try:
this_player = session.get(Player, player_id)
logger.info(f'Found player: {this_player}\nUpdating with db_player: {db_player}')
for key, value in db_player.model_dump(exclude_unset=True).items():
logger.info(f'Setting key ({key}) to value ({value})')
setattr(this_player, key, value)
logger.info(f'Set this_player to db_player')
session.add(this_player)
session.commit()
logger.info(f'Refreshing this_player')
session.refresh(this_player)
return this_player
except:
session.add(db_player)
session.commit()
session.refresh(db_player)
@ -426,10 +460,10 @@ async def get_or_create_ai_card(session: Session, player: Player, team: Team, sk
if tdelta.total_seconds() < CACHE_LIMIT and (this_card.pitcherscouting is not None or this_card.batterscouting is not None):
logger.info(f'returning this_card')
return this_card
else:
logger.info(f'deleting card record')
session.delete(this_card)
session.commit()
# else:
# logger.info(f'deleting card record')
# session.delete(this_card)
# session.commit()
async def pull_card(p: Player, t: Team):
c_query = await db_get('cards', params=[('team_id', t.id), ('player_id', p.id)])
@ -440,6 +474,25 @@ async def get_or_create_ai_card(session: Session, player: Player, team: Team, sk
json_data['player_id'] = get_player_id_from_dict(json_data['player'])
valid_card = CardBase.model_validate(c_query['cards'][0], from_attributes=True)
db_card = Card.model_validate(valid_card)
logger.info(f'gameplay_queries - cache_team - db_card: {db_card}')
logger.info(f'Checking for existing team ID: {db_card.id}')
try:
this_card = session.exec(select(Card).where(Card.id == db_card.id)).one()
logger.info(f'Found card: {this_card}\nUpdating with db card: {db_card}')
for key, value in db_card.model_dump(exclude_unset=True).items():
logger.info(f'Setting key ({key}) to value ({value})')
setattr(this_card, key, value)
logger.info(f'Set this_card to db_card')
session.add(this_card)
session.commit()
logger.info(f'Refreshing this_card')
session.refresh(this_card)
return this_card
except:
logger.info(f'Card not found, adding to db')
session.add(db_card)
session.commit()
session.refresh(db_card)
@ -494,6 +547,7 @@ async def get_or_create_ai_card(session: Session, player: Player, team: Team, sk
raise LookupError(err)
@log_errors
async def get_card_or_none(session: Session, card_id: int, skip_cache: bool = False) -> Card | None:
logger.info(f'Getting card {card_id} / skip_cache: {skip_cache}')
if not skip_cache:
@ -506,24 +560,44 @@ async def get_card_or_none(session: Session, card_id: int, skip_cache: bool = Fa
if tdelta.total_seconds() < CACHE_LIMIT and (this_card.pitcherscouting is not None or this_card.batterscouting is not None):
logger.info(f'returning this_card')
return this_card
else:
logger.info(f'deleting this_card')
try:
session.delete(this_card.batterscouting)
except Exception as e:
logger.error(f'Could not delete batter scouting: {e}')
# else:
# logger.info(f'deleting this_card')
# try:
# session.delete(this_card.batterscouting)
# except Exception as e:
# logger.error(f'Could not delete batter scouting: {e}')
try:
session.delete(this_card.pitcherscouting)
except Exception as e:
logger.error(f'Could not delete batter scouting: {e}')
# try:
# session.delete(this_card.pitcherscouting)
# except Exception as e:
# logger.error(f'Could not delete batter scouting: {e}')
session.delete(this_card)
session.commit()
# session.delete(this_card)
# session.commit()
def cache_card(json_data: dict) -> Card:
valid_card = CardBase.model_validate(json_data, from_attributes=True)
db_card = Card.model_validate(valid_card)
logger.info(f'gameplay_queries - cache_team - db_card: {db_card}')
logger.info(f'Checking for existing team ID: {db_card.id}')
try:
this_card = session.exec(select(Card).where(Card.id == db_card.id)).one()
logger.info(f'Found card: {this_card}\nUpdating with db card: {db_card}')
# this_team = db_team
for key, value in db_card.model_dump(exclude_unset=True).items():
logger.info(f'Setting key ({key}) to value ({value})')
setattr(this_card, key, value)
logger.info(f'Set this_card to db_card')
session.add(this_card)
session.commit()
logger.info(f'Refreshing this_card')
session.refresh(this_card)
return this_card
except:
logger.info(f'Card not found, adding to db')
session.add(db_card)
session.commit()
session.refresh(db_card)
@ -545,15 +619,22 @@ async def get_card_or_none(session: Session, card_id: int, skip_cache: bool = Fa
logger.info(f'Caching card ID {card_id} now')
this_card = cache_card(c_query)
logger.info(f'Card is cached, checking for scouting')
all_pos = [x for x in [this_player.pos_1, this_player.pos_2, this_player.pos_3, this_player.pos_3, this_player.pos_4, this_player.pos_5, this_player.pos_6, this_player.pos_7, this_player.pos_8] if x is not None]
logger.info(f'All positions: {all_pos}')
if 'SP' in all_pos or 'RP' in all_pos:
logger.info(f'Pulling pitcher scouting')
this_card.pitcherscouting = await shared_get_scouting(session, this_card, 'pitcher')
if any(item in all_pos for item in ['DH', 'C', '1B', '2B', '3B', 'SS', 'LF', 'CF', 'RF']):
logger.info(f'Pulling batter scouting')
this_card.batterscouting = await shared_get_scouting(session, this_card, 'batter')
logger.info(f'Updating this_card')
session.add(this_card)
session.commit()
logger.info(f'Refreshing this_card')
session.refresh(this_card)
logger.info(f'this_card: {this_card}')
return this_card

View File

@ -5,13 +5,14 @@ from sqlalchemy import pool
from alembic import context
from in_game.gameplay_models import sqlite_url, Game
from in_game.gameplay_models import sqlite_url, postgres_url
from sqlmodel import SQLModel
# this is the Alembic Config object, which provides
# access to the values within the .ini file in use.
config = context.config
config.set_main_option('sqlalchemy.url', sqlite_url)
# config.set_main_option('sqlalchemy.url', sqlite_url)
config.set_main_option('sqlalchemy.url', postgres_url)
# Interpret the config file for Python logging.
# This line sets up loggers basically.

View File

@ -9,7 +9,7 @@ from discord.utils import MISSING
from sqlmodel import Session
from api_calls import db_delete, db_get, db_post
from exceptions import CardNotFoundException, LegalityCheckNotRequired, PlayNotFoundException, log_exception
from exceptions import CardNotFoundException, LegalityCheckNotRequired, PlayNotFoundException, PositionNotFoundException, log_exception, log_errors
from helpers import get_card_embeds, random_insult
from in_game.game_helpers import legal_check
from in_game.gameplay_models import Game, Lineup, Play, Team
@ -262,14 +262,15 @@ class SelectReliefPitcher(discord.ui.Select):
log_exception(e, 'Couldn\'t clean up after selecting rp')
@log_errors
class SelectSubPosition(discord.ui.Select):
def __init__(self, session: Session, this_lineup: Lineup, custom_id = ..., placeholder = None, options: List[SelectOption] = ..., responders: list[discord.User] = None):
def __init__(self, session: Session, this_lineup: Lineup, custom_id = MISSING, placeholder = None, options: List[SelectOption] = ..., responders: list[discord.User] = None):
self.session = session
self.this_lineup = this_lineup
self.responders = responders
super().__init__(custom_id=custom_id, placeholder=placeholder, min_values=1, max_values=1, options=options, disabled=False)
@log_errors
async def callback(self, interaction: discord.Interaction):
if self.responders is not None and interaction.user not in self.responders:
await interaction.response.send_message(
@ -310,6 +311,7 @@ class SelectBatterSub(discord.ui.Select):
self.responders = responders
super().__init__(custom_id=custom_id, placeholder=placeholder, min_values=1, max_values=1, options=options)
@log_errors
async def callback(self, interaction: discord.Interaction):
if self.responders is not None and interaction.user not in self.responders:
await interaction.response.send_message(
@ -322,16 +324,20 @@ class SelectBatterSub(discord.ui.Select):
logger.info(f'Setting batter sub to Card ID: {self.values[0]}')
# Get Human batter card
logger.info(f'Looking up card substitution')
human_batter_card = await get_card_or_none(self.session, card_id=self.values[0])
if human_batter_card is None:
log_exception(CardNotFoundException, f'Card ID {self.values[0]} not found')
logger.info(f'human_batter_card: {human_batter_card}')
logger.info(f'Checking team ownership of batter card')
if human_batter_card.team_id != self.team.id:
logger.error(f'Card_id {self.values[0]} does not belong to {self.team.abbrev} in Game {self.game.id}')
await interaction.channel.send(
f'Uh oh. Card ID {self.values[0]} is {human_batter_card.player.name} and belongs to {human_batter_card.team.sname}. Will you double check that before we get started?'
)
return
logger.info(f'Ownership is good')
# legal_data = await legal_check([self.values[0]], difficulty_name=self.league_name)
# if not legal_data['legal']:
@ -340,6 +346,7 @@ class SelectBatterSub(discord.ui.Select):
# )
# return
logger.info(f'Pulling current play to log subs')
this_play = self.game.current_play_or_none(self.session)
if this_play is None:
log_exception(PlayNotFoundException, 'Play not found during substitution')
@ -360,10 +367,56 @@ class SelectBatterSub(discord.ui.Select):
)
if same_position:
logger.info(f'same_position is True')
position = last_lineup.position
pos_text = ''
view = None
else:
logger.info(f'same_position is False')
position = 'PH'
pos_text = 'What position will they play?'
logger.info(f'Deactivating last_lineup')
last_lineup.active = False
self.session.add(last_lineup)
logger.info(f'Set {last_lineup.card.player.name_with_desc} as inactive')
logger.info(f'new position: {position}')
if position not in ['DH', 'PR', 'PH']:
logger.info(f'go get position rating')
try:
pos_rating = await get_position(self.session, human_batter_card, position)
except PositionNotFoundException as e:
await interaction.edit_original_response(
content=f'Uh oh, I cannot find {position} ratings for {human_batter_card.player.name_with_desc}. Please go double-check this sub and run again.',
view=None
)
return
logger.info(f'Creating new lineup record')
human_bat_lineup = Lineup(
team=self.team,
player=human_batter_card.player,
card=human_batter_card,
position=position,
batting_order=self.batting_order,
game=self.game,
after_play=max(this_play.play_num - 1, 0),
replacing_id=last_lineup.id
)
logger.info(f'adding lineup to session: {human_bat_lineup}')
self.session.add(human_bat_lineup)
# self.session.commit()
logger.info(f'Inserted {human_bat_lineup.card.player.name_with_desc} in the {self.batting_order} spot')
this_play.batter = human_bat_lineup
this_play.batter_pos = position
logger.info(f'Adding play to session: {this_play}')
self.session.add(this_play)
self.session.commit()
if not same_position:
pos_dict_list = {
'Pinch Hitter': 'PH',
'Catcher': 'C',
@ -378,48 +431,35 @@ class SelectBatterSub(discord.ui.Select):
'Pitcher': 'P'
}
position = 'PH'
pos_text = 'What position will they play?'
options=[SelectSubPosition(label=f'{x}', value=pos_dict_list[x], default=x=='Pinch Hitter', responders=[interaction.user]) for x in pos_dict_list]
view = DropdownView(dropdown_objects=options)
last_lineup.active = False
self.session.add(last_lineup)
logger.info(f'Set {last_lineup.card.player.name_with_desc} as inactive')
if position not in ['DH', 'PR', 'PH']:
pos_rating = await get_position(self.session, human_batter_card, position)
human_bat_lineup = Lineup(
team=self.team,
player=human_batter_card.player,
card=human_batter_card,
position=position,
batting_order=self.batting_order,
game=self.game,
after_play=max(this_play.play_num - 1, 0),
replacing_id=last_lineup.id
logger.info(f'Prepping sub position')
pos_text = f'What position will {human_batter_card.player.name} play?'
options=[
SelectOption(
label=f'{pos_name}',
value=pos_dict_list[pos_name],
default=pos_name=='Pinch Hitter'
)
for pos_name in pos_dict_list.keys()
]
logger.info(f'options: {options}')
view = SelectSubPosition(
self.session,
this_lineup=human_bat_lineup,
placeholder='Select Position',
options=options,
responders=[interaction.user]
)
logger.info(f'new lineup: {human_bat_lineup}')
self.session.add(human_bat_lineup)
# self.session.commit()
try:
logger.info(f'Inserted {human_bat_lineup.card.player.name_with_desc} in the {self.batting_order} spot')
this_play.batter = human_bat_lineup
this_play.batter_pos = position
except Exception as e:
logger.error(e, exc_info=True, stack_info=True)
self.session.add(this_play)
self.session.commit()
logger.info(f'view: {view}')
logger.info(f'SelectSubPosition view is ready')
logger.info(f'Posting final sub message')
this_content = f'{human_batter_card.player.name_with_desc} has entered in the {self.batting_order} spot. {pos_text}'
await interaction.edit_original_response(
content=f'{human_batter_card.player.name_with_desc} has entered in the {self.batting_order} spot. {pos_text}',
content=this_content,
view=view
)
await interaction.channel.send(content='If you have additional subs to make, run `/substitution` again or run `/gamestate` to see the current plate appearance.')
class SelectPokemonEvolution(discord.ui.Select):