Coverage for an_website/hangman_solver/hangman_solver.py: 78.947%

57 statements  

« prev     ^ index     » next       coverage.py v7.6.4, created at 2024-11-16 19:56 +0000

1# This program is free software: you can redistribute it and/or modify 

2# it under the terms of the GNU Affero General Public License as 

3# published by the Free Software Foundation, either version 3 of the 

4# License, or (at your option) any later version. 

5# 

6# This program is distributed in the hope that it will be useful, 

7# but WITHOUT ANY WARRANTY; without even the implied warranty of 

8# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 

9# GNU Affero General Public License for more details. 

10# 

11# You should have received a copy of the GNU Affero General Public License 

12# along with this program. If not, see <https://www.gnu.org/licenses/>. 

13 

14"""A page that helps solving hangman puzzles.""" 

15 

16from __future__ import annotations 

17 

18from dataclasses import dataclass 

19 

20from hangman_solver import ( 

21 HangmanResult, 

22 Language, 

23 UnknownLanguageError, 

24 read_words_with_length, 

25 solve, 

26 solve_crossword, 

27) 

28from tornado.web import HTTPError 

29 

30from ..utils.base_request_handler import BaseRequestHandler 

31from ..utils.data_parsing import parse_args 

32from ..utils.request_handler import APIRequestHandler, HTMLRequestHandler 

33from ..utils.utils import ModuleInfo 

34 

35 

36def get_module_info() -> ModuleInfo: 

37 """Create and return the ModuleInfo for this module.""" 

38 languages = "|".join([lang.value.lower() for lang in Language.values()]) 

39 return ModuleInfo( 

40 handlers=( 

41 (r"/hangman-loeser", HangmanSolver), 

42 (r"/api/hangman-loeser", HangmanSolverAPI), 

43 ( 

44 rf"/hangman-loeser/worte/({languages})/([1-9]\d*).txt", 

45 HangmanSolverWords, 

46 ), 

47 ), 

48 name="Hangman-Löser", 

49 description="Eine Webseite, die Lösungen für Galgenmännchen findet", 

50 path="/hangman-loeser", 

51 keywords=("Galgenmännchen", "Hangman", "Löser", "Solver", "Worte"), 

52 aliases=( 

53 "/hangman-l%C3%B6ser", 

54 "/hangman-löser", 

55 "/hangman-solver", 

56 ), 

57 ) 

58 

59 

60@dataclass(slots=True) 

61class HangmanArguments: 

62 """Arguments for the hangman solver.""" 

63 

64 max_words: int = 20 

65 crossword_mode: bool = False 

66 lang: str = "de" 

67 input: str = "" 

68 invalid: str = "" 

69 

70 def get_max_words(self) -> int: 

71 """Return the maximum number of words.""" 

72 return max(0, min(100, self.max_words)) 

73 

74 

75def solve_hangman(data: HangmanArguments) -> HangmanResult: 

76 """Generate a hangman object based on the input and return it.""" 

77 try: 

78 lang = Language.parse_string(data.lang) 

79 except UnknownLanguageError as err: 

80 raise HTTPError( 

81 400, reason=f"{data.lang!r} is an invalid language" 

82 ) from err 

83 

84 return (solve_crossword if data.crossword_mode else solve)( 

85 data.input, list(data.invalid), lang, data.get_max_words() 

86 ) 

87 

88 

89class HangmanSolver(HTMLRequestHandler): 

90 """Request handler for the hangman solver page.""" 

91 

92 RATELIMIT_GET_LIMIT = 10 

93 

94 @parse_args(type_=HangmanArguments, name="data") 

95 async def get(self, *, data: HangmanArguments, head: bool = False) -> None: 

96 """Handle GET requests to the hangman solver page.""" 

97 if head: 

98 return 

99 

100 await self.render( 

101 "pages/hangman_solver.html", 

102 hangman_result=solve_hangman(data), 

103 data=data, 

104 ) 

105 

106 

107class HangmanSolverAPI(APIRequestHandler, HangmanSolver): 

108 """Request handler for the hangman solver API.""" 

109 

110 RATELIMIT_GET_LIMIT = 10 

111 

112 @parse_args(type_=HangmanArguments, name="data") 

113 async def get(self, *, data: HangmanArguments, head: bool = False) -> None: 

114 """Handle GET requests to the hangman solver API.""" 

115 if head: 

116 return 

117 hangman_result = solve_hangman(data) 

118 await self.finish( 

119 { 

120 "input": hangman_result.input, 

121 "invalid": "".join(hangman_result.invalid), 

122 "words": hangman_result.words, 

123 "word_count": hangman_result.matching_words_count, 

124 "letters": dict(hangman_result.letter_frequency), 

125 "crossword_mode": data.crossword_mode, 

126 "max_words": data.get_max_words(), 

127 "lang": hangman_result.language.value, 

128 } 

129 ) 

130 

131 

132class HangmanSolverWords(BaseRequestHandler): 

133 """Request handler for the hangman word lists.""" 

134 

135 RATELIMIT_GET_LIMIT = 15 

136 POSSIBLE_CONTENT_TYPES = ("text/plain",) 

137 

138 async def get( 

139 self, language: str, length: str, *, head: bool = False 

140 ) -> None: 

141 """Handle GET requests to the hangman solver page.""" 

142 # pylint: disable=unused-argument 

143 try: 

144 lang = Language.parse_string(language) 

145 except UnknownLanguageError as err: 

146 raise HTTPError(404) from err 

147 

148 try: 

149 word_length = int(length) 

150 except ValueError as err: 

151 raise HTTPError(404) from err 

152 

153 for word in read_words_with_length(lang, word_length): 

154 self.write(word) 

155 self.write(b"\n") 

156 

157 await self.finish()