paper-dynasty-discord/command_logic/play_context.py
Cal Corum ee80cd72ae fix: apply Black formatting and resolve ruff lint violations
Run Black formatter across 83 files and fix 1514 ruff violations:
- E722: bare except → typed exceptions (17 fixes)
- E711/E712/E721: comparison style fixes with noqa for SQLAlchemy (44 fixes)
- F841: unused variable assignments (70 fixes)
- F541/F401: f-string and import cleanup (1383 auto-fixes)

Remaining 925 errors are all F403/F405 (star imports) — structural,
requires converting to explicit imports in a separate effort.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-03-09 11:37:46 -05:00

70 lines
2.8 KiB
Python

"""
Async context manager for play locking. Wraps checks_log_interaction()
and guarantees lock release on exception, early return, or normal exit.
This module sits above play_lock and logic_gameplay to avoid circular imports.
"""
import logging
from contextlib import asynccontextmanager
from sqlmodel import Session
from play_lock import release_play_lock
from command_logic.logic_gameplay import checks_log_interaction
logger = logging.getLogger("discord_app")
@asynccontextmanager
async def locked_play(session: Session, interaction, command_name: str):
"""
Async context manager that acquires a play lock via checks_log_interaction
and guarantees release when the block exits.
Usage:
with Session(engine) as session:
async with locked_play(session, interaction, 'log flyball') as (game, team, play):
play = await flyballs(session, interaction, play, flyball_type)
await self.complete_and_post_play(session, interaction, play, ...)
Behavior:
- If checks_log_interaction raises (PlayLockedException, etc.), no lock
was acquired and the exception propagates naturally.
- If complete_play() was called, play.complete=True, so finally is a no-op.
- If the block raises an exception, the lock is released and the exception re-raised.
- If the block returns early (e.g. validation failure), the lock is released.
"""
# If this raises, no lock was acquired - exception propagates naturally
this_game, owner_team, this_play = await checks_log_interaction(
session, interaction, command_name=command_name, lock_play=True
)
try:
yield this_game, owner_team, this_play
except Exception as e:
if this_play.locked and not this_play.complete:
logger.error(
f"Exception in '{command_name}' on play {this_play.id}, releasing lock: {e}"
)
try:
release_play_lock(session, this_play)
except Exception as release_err:
logger.error(
f"Failed to release lock on play {this_play.id}: {release_err}"
)
raise
finally:
# Catches early returns (the primary bug fix)
# No-op if complete_play() already set complete=True and locked=False
if this_play.locked and not this_play.complete:
logger.warning(
f"Play {this_play.id} still locked after '{command_name}', "
f"releasing (likely early return)"
)
try:
release_play_lock(session, this_play)
except Exception as release_err:
logger.error(
f"Failed to force release lock on play {this_play.id}: {release_err}"
)