CI / Formatting (push) Successful in 5s
CI / Linting (push) Successful in 4s
CI / Tests (Python 3.12) (push) Successful in 2m54s
CI / Tests (Python 3.13) (push) Successful in 2m54s
CI / Tests (Python 3.14) (push) Successful in 2m52s
CI / Type Checking (push) Successful in 9s
CI / Spelling (push) Successful in 5s
127 lines
4.4 KiB
Python
127 lines
4.4 KiB
Python
# Copyright 2026 Logan Fick
|
|
#
|
|
# Licensed under the Apache License, Version 2.0 (the "License");
|
|
# you may not use this file except in compliance with the License.
|
|
# You may obtain a copy of the License at
|
|
#
|
|
# http://www.apache.org/licenses/LICENSE-2.0
|
|
#
|
|
# Unless required by applicable law or agreed to in writing, software
|
|
# distributed under the License is distributed on an "AS IS" BASIS,
|
|
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
# See the License for the specific language governing permissions and
|
|
# limitations under the License.
|
|
|
|
"""Synchronous in-memory repository for active poll data and recent results."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from .types import (
|
|
ActivePoll,
|
|
CompletedPoll,
|
|
DuplicateVoteError,
|
|
NoActivePollError,
|
|
PollAlreadyActiveError,
|
|
)
|
|
|
|
|
|
class PollRepository:
|
|
"""Store active poll state and the most recent completed result in memory."""
|
|
|
|
def __init__(self) -> None:
|
|
"""Initialize an empty repository."""
|
|
self._active_poll: ActivePoll | None = None
|
|
self._last_result: CompletedPoll | None = None
|
|
|
|
def get_active_poll(self) -> ActivePoll | None:
|
|
"""Return the active poll, if one exists.
|
|
|
|
:return: The active poll, or None if no poll is active.
|
|
"""
|
|
return self._active_poll
|
|
|
|
def create_active_poll(self, poll: ActivePoll) -> ActivePoll:
|
|
"""Store a new active poll.
|
|
|
|
:param poll: The poll to activate.
|
|
:return: The stored poll.
|
|
:raises PollAlreadyActiveError: If a poll is already active.
|
|
"""
|
|
if self._active_poll is not None:
|
|
raise PollAlreadyActiveError
|
|
self._active_poll = poll
|
|
return poll
|
|
|
|
def pop_active_poll(self) -> ActivePoll | None:
|
|
"""Clear and return the active poll, if one exists.
|
|
|
|
:return: The previously active poll, or None if no poll was active.
|
|
"""
|
|
poll = self._active_poll
|
|
if poll is None:
|
|
return None
|
|
self._active_poll = None
|
|
return poll
|
|
|
|
def get_vote(self, user_id: str) -> set[int] | None:
|
|
"""Return a copy of a user's selected options for the active poll.
|
|
|
|
:param user_id: ID of the user whose vote should be read.
|
|
:return: Selected option indices, or None if the user has not voted.
|
|
:raises NoActivePollError: If no poll is active.
|
|
"""
|
|
poll = self._require_active_poll()
|
|
selections = poll.votes.get(user_id)
|
|
return None if selections is None else set(selections)
|
|
|
|
def set_vote(self, user_id: str, selections: set[int]) -> None:
|
|
"""Store a user's selected options for the active poll.
|
|
|
|
:param user_id: ID of the user casting the vote.
|
|
:param selections: Selected option indices.
|
|
:raises NoActivePollError: If no poll is active.
|
|
:raises DuplicateVoteError: If the user already voted.
|
|
"""
|
|
poll = self._require_active_poll()
|
|
if user_id in poll.votes:
|
|
raise DuplicateVoteError(user_id)
|
|
poll.votes[user_id] = set(selections)
|
|
|
|
def list_votes(self) -> dict[str, set[int]]:
|
|
"""Return a copy of all user selections for the active poll.
|
|
|
|
:return: Mapping of user IDs to selected option indices.
|
|
:raises NoActivePollError: If no poll is active.
|
|
"""
|
|
poll = self._require_active_poll()
|
|
return {user_id: set(selections) for user_id, selections in poll.votes.items()}
|
|
|
|
def get_last_result(self) -> CompletedPoll | None:
|
|
"""Return the most recent completed result, if one exists.
|
|
|
|
:return: Most recent completed poll, or None if no result is available.
|
|
"""
|
|
return self._last_result
|
|
|
|
def set_last_result(self, result: CompletedPoll) -> None:
|
|
"""Store the most recent completed result.
|
|
|
|
:param result: Completed poll snapshot to expose on the results page.
|
|
"""
|
|
self._last_result = result
|
|
|
|
def clear_last_result(self) -> None:
|
|
"""Clear the most recent completed result."""
|
|
self._last_result = None
|
|
|
|
def _require_active_poll(self) -> ActivePoll:
|
|
"""Return the active poll or raise the domain no-active error.
|
|
|
|
:return: The currently active poll.
|
|
:raises NoActivePollError: If no poll is active.
|
|
"""
|
|
poll = self._active_poll
|
|
if poll is None:
|
|
raise NoActivePollError
|
|
return poll
|