paper-dynasty-discord/play_lock.py
Cal Corum 04d6cf3b5e fix: replace star imports with explicit named imports across codebase
Convert all `from x import *` to explicit imports in 12 files, resolving
925 F403/F405 ruff violations. Each name traced to its canonical source
module. Also fixes: duplicate Session import (players.py), missing
sample_team_data fixture param, duplicate method name paperdex_cardset_slash,
unused commands import in package __init__ files, and Play type hint in
play_lock.py.

3 pre-existing code bugs remain (F811 duplicate test names, F821 undefined
`question` variable) — these need investigation, not mechanical fixes.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 12:48:07 -05:00

83 lines
2.5 KiB
Python

"""
Play locking utilities to prevent concurrent play modifications.
This module is separate to avoid circular imports between logic_gameplay and utilities.
"""
from __future__ import annotations
import logging
from contextlib import contextmanager
from typing import TYPE_CHECKING
from sqlmodel import Session
if TYPE_CHECKING:
from in_game.gameplay_models import Play
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}")