refactor: simplify poll transition orchestration
This commit is contained in:
@@ -423,6 +423,93 @@ def _transition_for_idempotency_key(
|
||||
)
|
||||
|
||||
|
||||
def _transition_decision_option(
|
||||
poll: Poll,
|
||||
*,
|
||||
action: str,
|
||||
option_id: str | None,
|
||||
option_key: str | None,
|
||||
) -> PollOption | None:
|
||||
if action == "decide":
|
||||
return _option_by_id_or_key(poll, option_id=option_id, option_key=option_key)
|
||||
if option_id is not None or option_key is not None:
|
||||
raise PollError("Only a decide transition accepts a poll option")
|
||||
return None
|
||||
|
||||
|
||||
def _replayed_transition(
|
||||
poll: Poll,
|
||||
*,
|
||||
existing: PollLifecycleTransition | None,
|
||||
action: str,
|
||||
decision_option: PollOption | None,
|
||||
) -> PollTransitionResult | None:
|
||||
if existing is None:
|
||||
return None
|
||||
requested_option_id = decision_option.id if decision_option is not None else None
|
||||
if existing.action != action or existing.decision_option_id != requested_option_id:
|
||||
raise PollError("Idempotency key was already used for a different poll transition")
|
||||
return PollTransitionResult(poll=poll, transition=existing, replayed=True)
|
||||
|
||||
|
||||
def _clear_expired_close_for_open(poll: Poll, *, action: str, now: datetime) -> datetime | None:
|
||||
if action != "open" or poll.closes_at is None:
|
||||
return None
|
||||
normalized_closes_at = response_datetime(poll.closes_at)
|
||||
if normalized_closes_at is None or normalized_closes_at > now:
|
||||
return None
|
||||
poll.closes_at = None
|
||||
return normalized_closes_at
|
||||
|
||||
|
||||
def _apply_transition_state(
|
||||
poll: Poll,
|
||||
*,
|
||||
action: str,
|
||||
from_status: str,
|
||||
to_status: str,
|
||||
decision_option: PollOption | None,
|
||||
now: datetime,
|
||||
) -> None:
|
||||
if action == "archive":
|
||||
poll.archived_from_status = from_status
|
||||
elif action == "unarchive":
|
||||
poll.archived_from_status = None
|
||||
elif action == "close":
|
||||
poll.closed_at = now
|
||||
elif action == "decide":
|
||||
if decision_option is None:
|
||||
raise PollError("A decide transition requires a poll option")
|
||||
poll.decided_option_id = decision_option.id
|
||||
poll.decided_at = now
|
||||
if poll.closed_at is None:
|
||||
poll.closed_at = now
|
||||
poll.status = to_status
|
||||
|
||||
|
||||
def _transition_audit_metadata(
|
||||
*,
|
||||
action: str,
|
||||
from_status: str,
|
||||
to_status: str,
|
||||
used_legacy_unarchive_fallback: bool,
|
||||
cleared_expired_closes_at: datetime | None,
|
||||
) -> dict[str, Any]:
|
||||
metadata: dict[str, Any] = {}
|
||||
if action == "archive":
|
||||
metadata["archived_from_status"] = from_status
|
||||
elif action == "unarchive":
|
||||
metadata["restored_status"] = to_status
|
||||
if used_legacy_unarchive_fallback:
|
||||
metadata["legacy_restore_fallback"] = {
|
||||
"reason": "missing_archived_from_status",
|
||||
"restored_status": to_status,
|
||||
}
|
||||
if cleared_expired_closes_at is not None:
|
||||
metadata["cleared_expired_closes_at"] = cleared_expired_closes_at.isoformat()
|
||||
return metadata
|
||||
|
||||
|
||||
def transition_poll(
|
||||
session: Session,
|
||||
*,
|
||||
@@ -440,22 +527,26 @@ def transition_poll(
|
||||
|
||||
poll = _lock_poll_for_transition(session, tenant_id=tenant_id, poll_id=poll_id)
|
||||
normalized_key = _normalize_idempotency_key(idempotency_key)
|
||||
decision_option = None
|
||||
if action == "decide":
|
||||
decision_option = _option_by_id_or_key(poll, option_id=option_id, option_key=option_key)
|
||||
elif option_id is not None or option_key is not None:
|
||||
raise PollError("Only a decide transition accepts a poll option")
|
||||
decision_option = _transition_decision_option(
|
||||
poll,
|
||||
action=action,
|
||||
option_id=option_id,
|
||||
option_key=option_key,
|
||||
)
|
||||
|
||||
existing = _transition_for_idempotency_key(
|
||||
session,
|
||||
poll_id=poll.id,
|
||||
idempotency_key=normalized_key,
|
||||
)
|
||||
if existing is not None:
|
||||
requested_option_id = decision_option.id if decision_option is not None else None
|
||||
if existing.action != action or existing.decision_option_id != requested_option_id:
|
||||
raise PollError("Idempotency key was already used for a different poll transition")
|
||||
return PollTransitionResult(poll=poll, transition=existing, replayed=True)
|
||||
replayed = _replayed_transition(
|
||||
poll,
|
||||
existing=existing,
|
||||
action=action,
|
||||
decision_option=decision_option,
|
||||
)
|
||||
if replayed is not None:
|
||||
return replayed
|
||||
|
||||
try:
|
||||
plan = transition_engine.plan(
|
||||
@@ -469,40 +560,22 @@ def transition_poll(
|
||||
previous_decision_option_id = poll.decided_option_id
|
||||
now = _now()
|
||||
used_legacy_unarchive_fallback = action == "unarchive" and poll.archived_from_status is None
|
||||
cleared_expired_closes_at: datetime | None = None
|
||||
if action == "open" and poll.closes_at is not None:
|
||||
normalized_closes_at = response_datetime(poll.closes_at)
|
||||
if normalized_closes_at is not None and normalized_closes_at <= now:
|
||||
cleared_expired_closes_at = normalized_closes_at
|
||||
poll.closes_at = None
|
||||
|
||||
if action == "archive":
|
||||
poll.archived_from_status = plan.from_status
|
||||
elif action == "unarchive":
|
||||
poll.archived_from_status = None
|
||||
elif action == "close":
|
||||
poll.closed_at = now
|
||||
elif action == "decide":
|
||||
if decision_option is None:
|
||||
raise PollError("A decide transition requires a poll option")
|
||||
poll.decided_option_id = decision_option.id
|
||||
poll.decided_at = now
|
||||
if poll.closed_at is None:
|
||||
poll.closed_at = now
|
||||
|
||||
poll.status = plan.to_status
|
||||
transition_metadata: dict[str, Any] = {}
|
||||
if action == "archive":
|
||||
transition_metadata["archived_from_status"] = plan.from_status
|
||||
elif action == "unarchive":
|
||||
transition_metadata["restored_status"] = plan.to_status
|
||||
if used_legacy_unarchive_fallback:
|
||||
transition_metadata["legacy_restore_fallback"] = {
|
||||
"reason": "missing_archived_from_status",
|
||||
"restored_status": plan.to_status,
|
||||
}
|
||||
if cleared_expired_closes_at is not None:
|
||||
transition_metadata["cleared_expired_closes_at"] = cleared_expired_closes_at.isoformat()
|
||||
cleared_expired_closes_at = _clear_expired_close_for_open(poll, action=action, now=now)
|
||||
_apply_transition_state(
|
||||
poll,
|
||||
action=action,
|
||||
from_status=plan.from_status,
|
||||
to_status=plan.to_status,
|
||||
decision_option=decision_option,
|
||||
now=now,
|
||||
)
|
||||
transition_metadata = _transition_audit_metadata(
|
||||
action=action,
|
||||
from_status=plan.from_status,
|
||||
to_status=plan.to_status,
|
||||
used_legacy_unarchive_fallback=used_legacy_unarchive_fallback,
|
||||
cleared_expired_closes_at=cleared_expired_closes_at,
|
||||
)
|
||||
transition = PollLifecycleTransition(
|
||||
tenant_id=tenant_id,
|
||||
poll_id=poll.id,
|
||||
|
||||
Reference in New Issue
Block a user