Coverage for typer / _click / exceptions.py: 100%

119 statements  

« prev     ^ index     » next       coverage.py v7.13.1, created at 2026-11-09 00:15 +0000

1from collections.abc import Sequence 1abcdefg

2from typing import IO, TYPE_CHECKING, Any, Union 1abcdefg

3 

4from ..exceptions import TyperException 1abcdefg

5from ._compat import get_text_stderr 1abcdefg

6from .globals import resolve_color_default 1abcdefg

7from .utils import _escape_control_characters, echo, format_filename 1abcdefg

8 

9if TYPE_CHECKING: 1abcdefg

10 from .core import Command, Context, Parameter 

11 

12 

13def _join_param_hints(param_hint: Sequence[str] | str | None) -> str | None: 1abcdefg

14 if param_hint is not None and not isinstance(param_hint, str): 1abcdefg

15 return " / ".join(repr(x) for x in param_hint) 1abcdefg

16 

17 return param_hint 1abcdefg

18 

19 

20class ClickException(TyperException): 1abcdefg

21 """An exception that Click can handle and show to the user.""" 

22 

23 def __init__(self, message: str) -> None: 1abcdefg

24 super().__init__(message) 1abcdefg

25 # The context will be removed by the time we print the message, so cache 

26 # the color settings here to be used later on (in `show`) 

27 self.show_color: bool | None = resolve_color_default() 1abcdefg

28 

29 def show(self, file: IO[Any] | None = None) -> None: 1abcdefg

30 if file is None: 1abcdefg

31 file = get_text_stderr() 1abcdefg

32 

33 echo( 1abcdefg

34 f"Error: {self.format_message()}", 

35 file=file, 

36 color=self.show_color, 

37 ) 

38 

39 

40class UsageError(ClickException): 1abcdefg

41 """An internal exception that signals a usage error. This typically 

42 aborts any further handling. 

43 """ 

44 

45 exit_code = 2 1abcdefg

46 

47 def __init__(self, message: str, ctx: Union["Context", None] = None) -> None: 1abcdefg

48 super().__init__(message) 1abcdefg

49 self.ctx = ctx 1abcdefg

50 self.cmd: Command | None = self.ctx.command if self.ctx else None 1abcdefg

51 

52 def show(self, file: IO[Any] | None = None) -> None: 1abcdefg

53 if file is None: 1abcdefg

54 file = get_text_stderr() 1abcdefg

55 color = None 1abcdefg

56 hint = "" 1abcdefg

57 if ( 

58 self.ctx is not None 

59 and self.ctx.command.get_help_option(self.ctx) is not None 

60 ): 

61 command = self.ctx.command_path 1abcdefg

62 option = self.ctx.help_option_names[0] 1abcdefg

63 hint = f"Try '{command} {option}' for help.\n" 1abcdefg

64 if self.ctx is not None: 1abcdefg

65 color = self.ctx.color 1abcdefg

66 echo(f"{self.ctx.get_usage()}\n{hint}", file=file, color=color) 1abcdefg

67 echo( 1abcdefg

68 f"Error: {self.format_message()}", 

69 file=file, 

70 color=color, 

71 ) 

72 

73 

74class BadParameter(UsageError): 1abcdefg

75 """An exception that formats out a standardized error message for a 

76 bad parameter. This is useful when thrown from a callback or type as 

77 Click will attach contextual information to it (for instance, which 

78 parameter it is). 

79 """ 

80 

81 def __init__( 1abcdefg

82 self, 

83 message: str, 

84 ctx: Union["Context", None] = None, 

85 param: Union["Parameter", None] = None, 

86 param_hint: Sequence[str] | str | None = None, 

87 ) -> None: 

88 super().__init__(message, ctx) 1abcdefg

89 self.param = param 1abcdefg

90 self.param_hint = param_hint 1abcdefg

91 

92 def format_message(self) -> str: 1abcdefg

93 if self.param_hint is not None: 1abcdefg

94 param_hint = self.param_hint 1abcdefg

95 elif self.param is not None: 1abcdefg

96 param_hint = self.param.get_error_hint(self.ctx) # type: ignore 1abcdefg

97 else: 

98 return f"Invalid value: {self.message}" 1abcdefg

99 

100 hint = _join_param_hints(param_hint) 1abcdefg

101 return f"Invalid value for {hint}: {self.message}" 1abcdefg

102 

103 

104class MissingParameter(BadParameter): 1abcdefg

105 """Raised if click required an option or argument but it was not 

106 provided when invoking the script. 

107 """ 

108 

109 def __init__( 1abcdefg

110 self, 

111 message: str | None = None, 

112 ctx: Union["Context", None] = None, 

113 param: Union["Parameter", None] = None, 

114 param_hint: Sequence[str] | str | None = None, 

115 param_type: str | None = None, 

116 ) -> None: 

117 super().__init__(message or "", ctx, param, param_hint) 1abcdefg

118 self.param_type = param_type 1abcdefg

119 

120 def format_message(self) -> str: 1abcdefg

121 if self.param_hint is not None: 1abcdefg

122 param_hint: Sequence[str] | str | None = self.param_hint 1abcdefg

123 elif self.param is not None: 1abcdefg

124 param_hint = self.param.get_error_hint(self.ctx) # type: ignore 1abcdefg

125 else: 

126 param_hint = None 1abcdefg

127 

128 param_hint = _join_param_hints(param_hint) 1abcdefg

129 param_hint = f" {param_hint}" if param_hint else "" 1abcdefg

130 

131 param_type = self.param_type 1abcdefg

132 if param_type is None and self.param is not None: 1abcdefg

133 param_type = self.param.param_type_name 1abcdefg

134 

135 msg = self.message 1abcdefg

136 if self.param is not None: 1abcdefg

137 msg_extra = self.param.type.get_missing_message( 1abcdefg

138 param=self.param, ctx=self.ctx 

139 ) 

140 if msg_extra: 1abcdefg

141 if msg: 1abcdefg

142 msg += f". {msg_extra}" 1abcdefg

143 else: 

144 msg = msg_extra 1abcdefg

145 

146 msg = f" {msg}" if msg else "" 1abcdefg

147 

148 # Translate param_type for known types. 

149 if param_type == "argument": 1abcdefg

150 missing = "Missing argument" 1abcdefg

151 elif param_type == "option": 1abcdefg

152 missing = "Missing option" 1abcdefg

153 elif param_type == "parameter": 1abcdefg

154 missing = "Missing parameter" 1abcdefg

155 else: 

156 missing = f"Missing {param_type}" 1abcdefg

157 

158 return f"{missing}{param_hint}.{msg}" 1abcdefg

159 

160 def __str__(self) -> str: 1abcdefg

161 if not self.message: 1abcdefg

162 param_name = self.param.name if self.param else None 1abcdefg

163 return f"Missing parameter: {param_name}" 1abcdefg

164 else: 

165 return self.message 1abcdefg

166 

167 

168class NoSuchOption(UsageError): 1abcdefg

169 """Raised if click attempted to handle an option that does not 

170 exist. 

171 """ 

172 

173 def __init__( 1abcdefg

174 self, 

175 option_name: str, 

176 message: str | None = None, 

177 possibilities: Sequence[str] | None = None, 

178 ctx: Union["Context", None] = None, 

179 ) -> None: 

180 if message is None: 1abcdefg

181 message = f"No such option: {_escape_control_characters(option_name)}" 1abcdefg

182 

183 super().__init__(message, ctx) 1abcdefg

184 self.option_name = option_name 1abcdefg

185 self.possibilities = possibilities 1abcdefg

186 

187 def format_message(self) -> str: 1abcdefg

188 if not self.possibilities: 1abcdefg

189 return self.message 1abcdefg

190 

191 possibility_str = ", ".join(sorted(self.possibilities)) 1abcdefg

192 suggest = f"(Possible options: {possibility_str})" 1abcdefg

193 return f"{self.message} {suggest}" 1abcdefg

194 

195 

196class BadOptionUsage(UsageError): 1abcdefg

197 """Raised if an option is generally supplied but the use of the option 

198 was incorrect. This is for instance raised if the number of arguments 

199 for an option is not correct. 

200 """ 

201 

202 def __init__( 1abcdefg

203 self, option_name: str, message: str, ctx: Union["Context", None] = None 

204 ) -> None: 

205 super().__init__(message, ctx) 1abcdefg

206 self.option_name = option_name 1abcdefg

207 

208 

209class BadArgumentUsage(UsageError): 1abcdefg

210 """Raised if an argument is generally supplied but the use of the argument 

211 was incorrect. This is for instance raised if the number of values 

212 for an argument is not correct. 

213 """ 

214 

215 

216class NoArgsIsHelpError(UsageError): 1abcdefg

217 def __init__(self, ctx: "Context") -> None: 1abcdefg

218 self.ctx: Context 1abcdefg

219 super().__init__(ctx.get_help(), ctx=ctx) 1abcdefg

220 

221 def show(self, file: IO[Any] | None = None) -> None: 1abcdefg

222 echo(self.format_message(), file=file, err=True, color=self.ctx.color) 1abcdefg

223 

224 

225class FileError(ClickException): 1abcdefg

226 """Raised if a file cannot be opened.""" 

227 

228 def __init__(self, filename: str, hint: str | None = None) -> None: 1abcdefg

229 if hint is None: 1abcdefg

230 hint = "unknown error" 1abcdefg

231 

232 super().__init__(hint) 1abcdefg

233 self.ui_filename: str = format_filename(filename) 1abcdefg

234 self.filename = filename 1abcdefg

235 

236 def format_message(self) -> str: 1abcdefg

237 return f"Could not open file {self.ui_filename!r}: {self.message}" 1abcdefg