fix: address PR #177 review — move import os to top-level, add audit idempotency guard
- Move `import os` from inside evaluate_game() to module top-level imports (lazy imports are only for circular dependency avoidance) - Add get_or_none idempotency guard before RefractorBoostAudit.create() inside db.atomic() to prevent IntegrityError on UNIQUE(card_state, tier) constraint in PostgreSQL when apply_tier_boost is called twice for the same tier - Update atomicity test stub to provide card_state/tier attributes for the new Peewee expression in the idempotency guard Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
6a176af7da
commit
7f17c9b9f2
@ -1,3 +1,5 @@
|
|||||||
|
import os
|
||||||
|
|
||||||
from fastapi import APIRouter, Depends, HTTPException, Query
|
from fastapi import APIRouter, Depends, HTTPException, Query
|
||||||
import logging
|
import logging
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
@ -307,8 +309,6 @@ async def evaluate_game(game_id: int, token: str = Depends(oauth2_scheme)):
|
|||||||
logging.warning("Bad Token: [REDACTED]")
|
logging.warning("Bad Token: [REDACTED]")
|
||||||
raise HTTPException(status_code=401, detail="Unauthorized")
|
raise HTTPException(status_code=401, detail="Unauthorized")
|
||||||
|
|
||||||
import os
|
|
||||||
|
|
||||||
from ..db_engine import RefractorCardState, Player, StratPlay
|
from ..db_engine import RefractorCardState, Player, StratPlay
|
||||||
from ..services.refractor_boost import apply_tier_boost
|
from ..services.refractor_boost import apply_tier_boost
|
||||||
from ..services.refractor_evaluator import evaluate_card
|
from ..services.refractor_evaluator import evaluate_card
|
||||||
|
|||||||
@ -667,7 +667,11 @@ def apply_tier_boost(
|
|||||||
audit_data["battingcard"] = new_card.id
|
audit_data["battingcard"] = new_card.id
|
||||||
else:
|
else:
|
||||||
audit_data["pitchingcard"] = new_card.id
|
audit_data["pitchingcard"] = new_card.id
|
||||||
_audit_model.create(**audit_data)
|
existing_audit = _audit_model.get_or_none(
|
||||||
|
(_audit_model.card_state == card_state.id) & (_audit_model.tier == new_tier)
|
||||||
|
)
|
||||||
|
if existing_audit is None:
|
||||||
|
_audit_model.create(**audit_data)
|
||||||
|
|
||||||
# 8b. Update RefractorCardState — this is the SOLE tier write on tier-up.
|
# 8b. Update RefractorCardState — this is the SOLE tier write on tier-up.
|
||||||
card_state.current_tier = new_tier
|
card_state.current_tier = new_tier
|
||||||
|
|||||||
@ -1115,7 +1115,19 @@ class TestAtomicity:
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
class _FailingAuditModel:
|
class _FailingAuditModel:
|
||||||
"""Stub that raises on .create() to simulate audit write failure."""
|
"""Stub that raises on .create() to simulate audit write failure.
|
||||||
|
|
||||||
|
Provides card_state/tier attributes for the Peewee expression in the
|
||||||
|
idempotency guard, and get_or_none returns None so it proceeds to
|
||||||
|
create(), which then raises to simulate the failure.
|
||||||
|
"""
|
||||||
|
|
||||||
|
card_state = RefractorBoostAudit.card_state
|
||||||
|
tier = RefractorBoostAudit.tier
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def get_or_none(*args, **kwargs):
|
||||||
|
return None
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def create(**kwargs):
|
def create(**kwargs):
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user