swap out the NamedTuple for a frozen dataclass.

move the responsibility of sorting the numbers from the caller to the class initialisation.
(now the string representation matches the internal state)

move logging of ValueError from tui.py to lottery.py

patch bump
This commit is contained in:
onyx-and-iris 2026-02-23 16:53:33 +00:00
parent 8b743abcfb
commit a371a4f60c
3 changed files with 22 additions and 17 deletions

View File

@ -1,6 +1,6 @@
[project] [project]
name = "lottery-tui" name = "lottery-tui"
version = "0.2.6" version = "0.2.7"
description = "A terminal user interface for lottery games." description = "A terminal user interface for lottery games."
authors = [{ name = "onyx-and-iris", email = "code@onyxandiris.online" }] authors = [{ name = "onyx-and-iris", email = "code@onyxandiris.online" }]
dependencies = ["textual>=8.0.0", "loguru>=0.7.3"] dependencies = ["textual>=8.0.0", "loguru>=0.7.3"]

View File

@ -1,18 +1,30 @@
import random import random
from abc import ABC, abstractmethod from abc import ABC, abstractmethod
from typing import NamedTuple from dataclasses import dataclass
from loguru import logger
class Result(NamedTuple): @dataclass(frozen=True)
"""A named tuple to hold the results of a lottery draw.""" class Result:
"""A dataclass to hold the results of a lottery draw with auto-sorted numbers."""
kind: str kind: str
numbers: list[int] numbers: list[int]
bonus: list[int] | None bonus: list[int] | None
def __post_init__(self):
"""Sort the numbers after initialization.
We use super().__setattr__ to bypass the frozen nature of the dataclass for sorting.
"""
super().__setattr__('numbers', sorted(self.numbers))
if self.bonus:
super().__setattr__('bonus', sorted(self.bonus))
def __str__(self) -> str: def __str__(self) -> str:
"""Return a string representation of the lottery result.""" """Return a string representation of the lottery result."""
out = f'Numbers: {", ".join(str(n) for n in sorted(self.numbers))}' out = f'Numbers: {", ".join(str(n) for n in self.numbers)}'
if self.bonus: if self.bonus:
match self.kind: match self.kind:
case 'EuroMillions': case 'EuroMillions':
@ -23,7 +35,7 @@ class Result(NamedTuple):
bonus_name = 'Thunderball' bonus_name = 'Thunderball'
case _: case _:
bonus_name = 'Bonus Numbers' bonus_name = 'Bonus Numbers'
out += f'\n{bonus_name}: {", ".join(str(n) for n in sorted(self.bonus))}' out += f'\n{bonus_name}: {", ".join(str(n) for n in self.bonus)}'
return out return out
@ -116,5 +128,7 @@ def request_lottery_obj(lottery_name: str) -> Lottery:
"""Return a lottery object based on the provided lottery name.""" """Return a lottery object based on the provided lottery name."""
lottery_cls = registry.get(lottery_name.lower()) lottery_cls = registry.get(lottery_name.lower())
if lottery_cls is None: if lottery_cls is None:
raise ValueError(f"Lottery '{lottery_name}' not found.") ERR_MSG = f"Lottery '{lottery_name}' not found. Available lotteries: {', '.join(registry.keys())}"
logger.error(ERR_MSG)
raise ValueError(ERR_MSG)
return lottery_cls() return lottery_cls()

View File

@ -1,4 +1,3 @@
from loguru import logger
from rich.text import Text from rich.text import Text
from textual.app import App, ComposeResult from textual.app import App, ComposeResult
from textual.containers import Container from textual.containers import Container
@ -49,15 +48,7 @@ class LotteryTUI(App):
) )
return return
selected_lottery = self.query_one('#lottery-select').value lottery_obj = request_lottery_obj(self.query_one('#lottery-select').value)
try:
lottery_obj = request_lottery_obj(selected_lottery)
except ValueError:
ERR_MSG = f'Invalid lottery selection: {selected_lottery}'
logger.exception(ERR_MSG)
raise
result = lottery_obj.draw() result = lottery_obj.draw()
self._update_result_label(str(result)) self._update_result_label(str(result))