Refactored polls module into layered architecture with typed domain objects, isolated SSE handling, and comprehensive tests.
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
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
This commit is contained in:
@@ -0,0 +1,126 @@
|
||||
# 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
|
||||
Reference in New Issue
Block a user