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>
70 lines
2.8 KiB
Python
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}"
|
|
)
|