Coverage for tests/test_type_conversion.py: 100%

101 statements  

« prev     ^ index     » next       coverage.py v7.6.1, created at 2024-09-09 18:26 +0000

1from enum import Enum 1habfcgde

2from pathlib import Path 1habfcgde

3from typing import Any, List, Optional, Tuple 1habfcgde

4 

5import click 1habfcgde

6import pytest 1habfcgde

7import typer 1habfcgde

8from typer.testing import CliRunner 1habfcgde

9 

10from .utils import needs_py310 1habfcgde

11 

12runner = CliRunner() 1habfcgde

13 

14 

15def test_optional(): 1habfcgde

16 app = typer.Typer() 1habfcgde

17 

18 @app.command() 1habfcgde

19 def opt(user: Optional[str] = None): 1habfcgde

20 if user: 1habfcgde

21 print(f"User: {user}") 1habfcgde

22 else: 

23 print("No user") 1habfcgde

24 

25 result = runner.invoke(app) 1habfcgde

26 assert result.exit_code == 0 1habfcgde

27 assert "No user" in result.output 1habfcgde

28 

29 result = runner.invoke(app, ["--user", "Camila"]) 1habfcgde

30 assert result.exit_code == 0 1habfcgde

31 assert "User: Camila" in result.output 1habfcgde

32 

33 

34@needs_py310 1habfcgde

35def test_union_type_optional(): 1abfcgde

36 app = typer.Typer() 1abcde

37 

38 @app.command() 1abcde

39 def opt(user: str | None = None): 1abcde

40 if user: 1abcde

41 print(f"User: {user}") 1abcde

42 else: 

43 print("No user") 1abcde

44 

45 result = runner.invoke(app) 1abcde

46 assert result.exit_code == 0 1abcde

47 assert "No user" in result.output 1abcde

48 

49 result = runner.invoke(app, ["--user", "Camila"]) 1abcde

50 assert result.exit_code == 0 1abcde

51 assert "User: Camila" in result.output 1abcde

52 

53 

54def test_optional_tuple(): 1habfcgde

55 app = typer.Typer() 1habfcgde

56 

57 @app.command() 1habfcgde

58 def opt(number: Optional[Tuple[int, int]] = None): 1habfcgde

59 if number: 1habfcgde

60 print(f"Number: {number}") 1habfcgde

61 else: 

62 print("No number") 1habfcgde

63 

64 result = runner.invoke(app) 1habfcgde

65 assert result.exit_code == 0 1habfcgde

66 assert "No number" in result.output 1habfcgde

67 

68 result = runner.invoke(app, ["--number", "4", "2"]) 1habfcgde

69 assert result.exit_code == 0 1habfcgde

70 assert "Number: (4, 2)" in result.output 1habfcgde

71 

72 

73def test_no_type(): 1habfcgde

74 app = typer.Typer() 1habfcgde

75 

76 @app.command() 1habfcgde

77 def no_type(user): 1abfcgde

78 print(f"User: {user}") 1habfcgde

79 

80 result = runner.invoke(app, ["Camila"]) 1habfcgde

81 assert result.exit_code == 0 1habfcgde

82 assert "User: Camila" in result.output 1habfcgde

83 

84 

85class SomeEnum(Enum): 1habfcgde

86 ONE = "one" 1habfcgde

87 TWO = "two" 1habfcgde

88 THREE = "three" 1habfcgde

89 

90 

91@pytest.mark.parametrize( 1habfcgde

92 "type_annotation", 

93 [List[Path], List[SomeEnum], List[str]], 

94) 

95def test_list_parameters_convert_to_lists(type_annotation): 1abfcgde

96 # Lists containing objects that are converted by Click (i.e. not Path or Enum) 

97 # should not be inadvertently converted to tuples 

98 expected_element_type = type_annotation.__args__[0] 1habfcgde

99 app = typer.Typer() 1habfcgde

100 

101 @app.command() 1habfcgde

102 def list_conversion(container: type_annotation): 1habfcgde

103 assert isinstance(container, list) 1habfcgde

104 for element in container: 1habfcgde

105 assert isinstance(element, expected_element_type) 1habfcgde

106 

107 result = runner.invoke(app, ["one", "two", "three"]) 1habfcgde

108 assert result.exit_code == 0 1habfcgde

109 

110 

111@pytest.mark.parametrize( 1habfcgde

112 "type_annotation", 

113 [ 

114 Tuple[str, str], 

115 Tuple[str, Path], 

116 Tuple[Path, Path], 

117 Tuple[str, SomeEnum], 

118 Tuple[SomeEnum, SomeEnum], 

119 ], 

120) 

121def test_tuple_parameter_elements_are_converted_recursively(type_annotation): 1abfcgde

122 # Tuple elements that aren't converted by Click (i.e. Path or Enum) 

123 # should be recursively converted by Typer 

124 expected_element_types = type_annotation.__args__ 1habfcgde

125 app = typer.Typer() 1habfcgde

126 

127 @app.command() 1habfcgde

128 def tuple_recursive_conversion(container: type_annotation): 1habfcgde

129 assert isinstance(container, tuple) 1habfcgde

130 for element, expected_type in zip(container, expected_element_types): 1habfcgde

131 assert isinstance(element, expected_type) 1habfcgde

132 

133 result = runner.invoke(app, ["one", "two"]) 1habfcgde

134 assert result.exit_code == 0 1habfcgde

135 

136 

137def test_custom_parse(): 1habfcgde

138 app = typer.Typer() 1habfcgde

139 

140 @app.command() 1habfcgde

141 def custom_parser( 1abfcgde

142 hex_value: int = typer.Argument(None, parser=lambda x: int(x, 0)), 

143 ): 

144 assert hex_value == 0x56 1habfcgde

145 

146 result = runner.invoke(app, ["0x56"]) 1habfcgde

147 assert result.exit_code == 0 1habfcgde

148 

149 

150def test_custom_click_type(): 1habfcgde

151 class BaseNumberParamType(click.ParamType): 1habfcgde

152 name = "base_integer" 1habfcgde

153 

154 def convert( 1abfcgde

155 self, 

156 value: Any, 

157 param: Optional[click.Parameter], 

158 ctx: Optional[click.Context], 

159 ) -> Any: 

160 return int(value, 0) 1habfcgde

161 

162 app = typer.Typer() 1habfcgde

163 

164 @app.command() 1habfcgde

165 def custom_click_type( 1abfcgde

166 hex_value: int = typer.Argument(None, click_type=BaseNumberParamType()), 

167 ): 

168 assert hex_value == 0x56 1habfcgde

169 

170 result = runner.invoke(app, ["0x56"]) 1habfcgde

171 assert result.exit_code == 0 1habfcgde