Fix circular import by moving play lock functions to separate module
HOTFIX: Production bot failed to start due to circular import. Root cause: utilities/dropdown.py importing from command_logic/logic_gameplay.py while logic_gameplay.py imports from utilities/dropdown.py. Solution: Created play_lock.py as standalone module containing: - release_play_lock() - safe_play_lock() Both modules now import from play_lock.py instead of each other. Error message: ImportError: cannot import name 'release_play_lock' from partially initialized module 'command_logic.logic_gameplay' (most likely due to a circular import) Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
48c86e54fd
commit
89801e1d42
@ -14,7 +14,8 @@ import sqlalchemy
|
|||||||
from sqlmodel import func, or_
|
from sqlmodel import func, or_
|
||||||
|
|
||||||
from api_calls import db_get
|
from api_calls import db_get
|
||||||
from command_logic.logic_gameplay import bunts, chaos, complete_game, defender_dropdown_view, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, release_play_lock, relief_pitcher_dropdown_view, safe_play_lock, select_ai_reliever, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, substitute_player, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play
|
from command_logic.logic_gameplay import bunts, chaos, complete_game, defender_dropdown_view, doubles, flyballs, frame_checks, get_full_roster_from_sheets, checks_log_interaction, complete_play, get_scorebug_embed, groundballs, hit_by_pitch, homeruns, is_game_over, lineouts, manual_end_game, new_game_checks, new_game_conflicts, popouts, read_lineup, relief_pitcher_dropdown_view, select_ai_reliever, show_defense_cards, singles, starting_pitcher_dropdown_view, steals, strikeouts, sub_batter_dropdown_view, substitute_player, triples, undo_play, update_game_settings, walks, xchecks, activate_last_play
|
||||||
|
from play_lock import release_play_lock, safe_play_lock
|
||||||
from dice import ab_roll
|
from dice import ab_roll
|
||||||
from exceptions import *
|
from exceptions import *
|
||||||
import gauntlets
|
import gauntlets
|
||||||
|
|||||||
@ -1,6 +1,5 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
import copy
|
import copy
|
||||||
from contextlib import contextmanager
|
|
||||||
import logging
|
import logging
|
||||||
import discord
|
import discord
|
||||||
from discord import SelectOption
|
from discord import SelectOption
|
||||||
@ -13,6 +12,7 @@ from typing import Literal
|
|||||||
from api_calls import db_delete, db_get, db_post
|
from api_calls import db_delete, db_get, db_post
|
||||||
from dice import DTwentyRoll, d_twenty_roll, frame_plate_check, sa_fielding_roll
|
from dice import DTwentyRoll, d_twenty_roll, frame_plate_check, sa_fielding_roll
|
||||||
from exceptions import *
|
from exceptions import *
|
||||||
|
from play_lock import release_play_lock, safe_play_lock
|
||||||
from gauntlets import post_result
|
from gauntlets import post_result
|
||||||
from helpers import (
|
from helpers import (
|
||||||
COLORS,
|
COLORS,
|
||||||
@ -81,58 +81,6 @@ WPA_DF = pd.read_csv(f"storage/wpa_data.csv").set_index("index")
|
|||||||
TO_BASE = {2: "to second", 3: "to third", 4: "home"}
|
TO_BASE = {2: "to second", 3: "to third", 4: "home"}
|
||||||
|
|
||||||
|
|
||||||
@contextmanager
|
|
||||||
def safe_play_lock(session: Session, play: "Play"):
|
|
||||||
"""
|
|
||||||
Context manager for safely handling play locks.
|
|
||||||
|
|
||||||
Ensures the lock is ALWAYS released even if an exception occurs during
|
|
||||||
command processing. This prevents permanent user lockouts.
|
|
||||||
|
|
||||||
Usage:
|
|
||||||
with safe_play_lock(session, this_play):
|
|
||||||
# Do command processing
|
|
||||||
# Lock will be released automatically on exception or normal exit
|
|
||||||
pass
|
|
||||||
|
|
||||||
Note: This only releases the lock on exception. On successful completion,
|
|
||||||
complete_play() should handle releasing the lock and marking the play complete.
|
|
||||||
"""
|
|
||||||
lock_released = False
|
|
||||||
try:
|
|
||||||
yield play
|
|
||||||
except Exception as e:
|
|
||||||
# Release lock on any exception
|
|
||||||
if play.locked and not play.complete:
|
|
||||||
logger.error(
|
|
||||||
f"Exception during play {play.id} processing, releasing lock. Error: {e}"
|
|
||||||
)
|
|
||||||
play.locked = False
|
|
||||||
session.add(play)
|
|
||||||
try:
|
|
||||||
session.commit()
|
|
||||||
lock_released = True
|
|
||||||
except Exception as commit_error:
|
|
||||||
logger.error(
|
|
||||||
f"Failed to release lock on play {play.id}: {commit_error}"
|
|
||||||
)
|
|
||||||
raise # Re-raise the original exception
|
|
||||||
finally:
|
|
||||||
# Final safety check
|
|
||||||
if not lock_released and play.locked and not play.complete:
|
|
||||||
logger.warning(
|
|
||||||
f"Play {play.id} still locked after processing, forcing unlock"
|
|
||||||
)
|
|
||||||
play.locked = False
|
|
||||||
session.add(play)
|
|
||||||
try:
|
|
||||||
session.commit()
|
|
||||||
except Exception as commit_error:
|
|
||||||
logger.error(
|
|
||||||
f"Failed to force unlock play {play.id}: {commit_error}"
|
|
||||||
)
|
|
||||||
|
|
||||||
|
|
||||||
def safe_wpa_lookup(
|
def safe_wpa_lookup(
|
||||||
inning_half: str,
|
inning_half: str,
|
||||||
inning_num: int,
|
inning_num: int,
|
||||||
@ -1248,20 +1196,6 @@ async def get_full_roster_from_sheets(
|
|||||||
).all()
|
).all()
|
||||||
|
|
||||||
|
|
||||||
def release_play_lock(session: Session, play: Play) -> None:
|
|
||||||
"""
|
|
||||||
Release a play lock and commit the change.
|
|
||||||
|
|
||||||
This is a safety mechanism to ensure locks are always released,
|
|
||||||
even when exceptions occur during command processing.
|
|
||||||
"""
|
|
||||||
if play.locked:
|
|
||||||
logger.info(f"Releasing lock on play {play.id}")
|
|
||||||
play.locked = False
|
|
||||||
session.add(play)
|
|
||||||
session.commit()
|
|
||||||
|
|
||||||
|
|
||||||
async def checks_log_interaction(
|
async def checks_log_interaction(
|
||||||
session: Session, interaction: discord.Interaction, command_name: str, lock_play: bool = True
|
session: Session, interaction: discord.Interaction, command_name: str, lock_play: bool = True
|
||||||
) -> tuple[Game, Team, Play]:
|
) -> tuple[Game, Team, Play]:
|
||||||
|
|||||||
76
play_lock.py
Normal file
76
play_lock.py
Normal file
@ -0,0 +1,76 @@
|
|||||||
|
"""
|
||||||
|
Play locking utilities to prevent concurrent play modifications.
|
||||||
|
|
||||||
|
This module is separate to avoid circular imports between logic_gameplay and utilities.
|
||||||
|
"""
|
||||||
|
import logging
|
||||||
|
from contextlib import contextmanager
|
||||||
|
from sqlmodel import Session
|
||||||
|
|
||||||
|
logger = logging.getLogger("discord_app")
|
||||||
|
|
||||||
|
|
||||||
|
def release_play_lock(session: Session, play: "Play") -> None:
|
||||||
|
"""
|
||||||
|
Release a play lock and commit the change.
|
||||||
|
|
||||||
|
This is a safety mechanism to ensure locks are always released,
|
||||||
|
even when exceptions occur during command processing.
|
||||||
|
"""
|
||||||
|
if play.locked:
|
||||||
|
logger.info(f"Releasing lock on play {play.id}")
|
||||||
|
play.locked = False
|
||||||
|
session.add(play)
|
||||||
|
session.commit()
|
||||||
|
|
||||||
|
|
||||||
|
@contextmanager
|
||||||
|
def safe_play_lock(session: Session, play: "Play"):
|
||||||
|
"""
|
||||||
|
Context manager for safely handling play locks.
|
||||||
|
|
||||||
|
Ensures the lock is ALWAYS released even if an exception occurs during
|
||||||
|
command processing. This prevents permanent user lockouts.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
with safe_play_lock(session, this_play):
|
||||||
|
# Do command processing
|
||||||
|
# Lock will be released automatically on exception or normal exit
|
||||||
|
pass
|
||||||
|
|
||||||
|
Note: This only releases the lock on exception. On successful completion,
|
||||||
|
complete_play() should handle releasing the lock and marking the play complete.
|
||||||
|
"""
|
||||||
|
lock_released = False
|
||||||
|
try:
|
||||||
|
yield play
|
||||||
|
except Exception as e:
|
||||||
|
# Release lock on any exception
|
||||||
|
if play.locked and not play.complete:
|
||||||
|
logger.error(
|
||||||
|
f"Exception during play {play.id} processing, releasing lock. Error: {e}"
|
||||||
|
)
|
||||||
|
play.locked = False
|
||||||
|
session.add(play)
|
||||||
|
try:
|
||||||
|
session.commit()
|
||||||
|
lock_released = True
|
||||||
|
except Exception as commit_error:
|
||||||
|
logger.error(
|
||||||
|
f"Failed to release lock on play {play.id}: {commit_error}"
|
||||||
|
)
|
||||||
|
raise # Re-raise the original exception
|
||||||
|
finally:
|
||||||
|
# Final safety check
|
||||||
|
if not lock_released and play.locked and not play.complete:
|
||||||
|
logger.warning(
|
||||||
|
f"Play {play.id} still locked after processing, forcing unlock"
|
||||||
|
)
|
||||||
|
play.locked = False
|
||||||
|
session.add(play)
|
||||||
|
try:
|
||||||
|
session.commit()
|
||||||
|
except Exception as commit_error:
|
||||||
|
logger.error(
|
||||||
|
f"Failed to force unlock play {play.id}: {commit_error}"
|
||||||
|
)
|
||||||
@ -9,8 +9,8 @@ from discord.utils import MISSING
|
|||||||
from sqlmodel import Session
|
from sqlmodel import Session
|
||||||
|
|
||||||
from api_calls import db_delete, db_get, db_post
|
from api_calls import db_delete, db_get, db_post
|
||||||
from command_logic.logic_gameplay import release_play_lock, safe_play_lock
|
|
||||||
from exceptions import CardNotFoundException, LegalityCheckNotRequired, LineupsMissingException, PlayNotFoundException, PositionNotFoundException, log_exception, log_errors
|
from exceptions import CardNotFoundException, LegalityCheckNotRequired, LineupsMissingException, PlayNotFoundException, PositionNotFoundException, log_exception, log_errors
|
||||||
|
from play_lock import release_play_lock, safe_play_lock
|
||||||
from helpers import DEFENSE_NO_PITCHER_LITERAL, get_card_embeds, position_name_to_abbrev, random_insult
|
from helpers import DEFENSE_NO_PITCHER_LITERAL, get_card_embeds, position_name_to_abbrev, random_insult
|
||||||
from in_game.game_helpers import legal_check
|
from in_game.game_helpers import legal_check
|
||||||
from in_game.gameplay_models import Game, Lineup, Play, Team
|
from in_game.gameplay_models import Game, Lineup, Play, Team
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user